1. 简介
在编译 Java 源代码时,有时会看到编译器输出类似这样的警告信息:
“unchecked conversion” 或 “The expression of type List needs unchecked conversion”
这类警告提示我们,编译器在类型转换过程中没有进行完整的类型检查。本文将深入探讨这个警告的含义、可能引发的问题,以及如何正确处理它。
2. 开启 unchecked 警告选项
在深入分析这个警告前,我们先确认是否已经开启了相关的编译器警告选项。
✅ 如果你使用的是 Eclipse JDT 编译器,这个警告默认是开启的。
❌ 如果你使用的是 Oracle 或 OpenJDK 的 javac
编译器,则需要手动添加 -Xlint:unchecked
选项来启用该警告。
在 IDE 中配置
大多数开发者使用 IDE 编写代码,可以在 IDE 的编译器设置中添加该选项。例如,在 JetBrains IntelliJ IDEA 中的配置如下图所示:
在 Maven 中配置
如果你使用 Apache Maven 构建项目,可以通过配置 maven-compiler-plugin
插件的 compilerArguments
来启用该选项:
<build>
...
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
...
<configuration>
...
<compilerArguments>
<Xlint:unchecked/>
</compilerArguments>
</configuration>
</plugin>
</plugins>
</build>
现在我们已经确认开启了相关警告,接下来我们看看这个警告会在什么情况下出现。
3. 什么时候会出现 “unchecked conversion” 警告?
这个警告是一个 编译时警告,通常出现在你将一个 原始类型(raw type)赋值给一个泛型类型(parameterized type) 的时候,而没有进行类型检查。
编译器之所以允许这种赋值,是为了 保持与不支持泛型的老版本 Java 的兼容性。
来看一个例子。假设我们有一个返回原始类型 List
的方法:
public class UncheckedConversion {
public static List getRawList() {
List result = new ArrayList();
result.add("I am the 1st String.");
result.add("I am the 2nd String.");
result.add("I am the 3rd String.");
return result;
}
...
}
然后我们调用这个方法,并将其赋值给一个泛型类型 List<String>
:
@Test
public void givenRawList_whenAssignToTypedList_shouldHaveCompilerWarning() {
List<String> fromRawList = UncheckedConversion.getRawList();
Assert.assertEquals(3, fromRawList.size());
Assert.assertEquals("I am the 1st String.", fromRawList.get(0));
}
编译这段代码时,就会看到如下警告:
[WARNING] .../UncheckedConversionDemoUnitTest.java:[12,66] unchecked conversion
required: java.util.List<java.lang.String>
found: java.util.List
一个常见的实际场景是使用 Java Persistence API 的 Query.getResultList()
方法。该方法返回的是原始类型 List
,当我们试图将其赋值给泛型类型时,就会触发这个警告:
List<MyEntity> results = entityManager.createNativeQuery("... SQL ...", MyEntity.class).getResultList();
虽然程序可以正常运行,但潜在风险依然存在。我们接着看。
4. 为什么编译器会发出警告?
虽然上一节的测试方法能正常运行,但这只是因为 getRawList()
方法只添加了 String
类型元素。
如果我们修改一下方法,加入一个 Date
对象:
public static List getRawListWithMixedTypes() {
List result = new ArrayList();
result.add("I am the 1st String.");
result.add("I am the 2nd String.");
result.add("I am the 3rd String.");
result.add(new Date());
return result;
}
然后测试:
@Test(expected = ClassCastException.class)
public void givenRawList_whenListHasMixedType_shouldThrowClassCastException() {
List<String> fromRawList = UncheckedConversion.getRawListWithMixedTypes();
Assert.assertEquals(4, fromRawList.size());
Assert.assertFalse(fromRawList.get(3).endsWith("String."));
}
运行测试时,会再次看到 “unchecked conversion” 警告,并且最终会抛出 ClassCastException
。
⚠️ 在实际项目中,这种异常可能在程序运行到一半才抛出,导致已经处理了部分数据,造成脏数据或事务回滚失败等严重后果。
5. 如何处理这个警告?
5.1. 修改原始方法为泛型方法(推荐)
如果可以修改原始方法,应该将其改造成泛型方法,这样可以从根本上保证类型安全。
5.2. 如果无法修改(如第三方库)
如果你无法修改原始方法,只能在调用时处理这个警告。
✅ 使用 @SuppressWarnings("unchecked")
抑制警告
只有在你 完全确定类型转换是安全的 情况下,才使用这个注解:
Query query = entityManager.createQuery("SELECT e.field1, e.field2, e.field3 FROM SomeEntity e");
@SuppressWarnings("unchecked")
List<Object[]> list = query.list();
✅ 手动进行类型检查
可以通过遍历原始集合并手动进行类型转换,来避免潜在的 ClassCastException
。
下面是一个通用的类型转换方法,会过滤掉类型不匹配的元素:
public static <T> List<T> castList(Class<? extends T> clazz, Collection<?> rawCollection) {
List<T> result = new ArrayList<>(rawCollection.size());
for (Object o : rawCollection) {
try {
result.add(clazz.cast(o));
} catch (ClassCastException e) {
// log the exception or other error handling
}
}
return result;
}
测试方法如下:
@Test
public void givenRawList_whenAssignToTypedListAfterCallingCastList_shouldOnlyHaveElementsWithExpectedType() {
List rawList = UncheckedConversion.getRawListWithMixedTypes();
List<String> strList = UncheckedConversion.castList(String.class, rawList);
Assert.assertEquals(4, rawList.size());
Assert.assertEquals("One element with the wrong type has been filtered out.", 3, strList.size());
Assert.assertTrue(strList.stream().allMatch(el -> el.endsWith("String.")));
}
或者,如果你希望在遇到类型错误时立即抛出异常,可以使用这个版本:
public static <T> List<T> castList2(Class<? extends T> clazz, Collection<?> rawCollection)
throws ClassCastException {
List<T> result = new ArrayList<>(rawCollection.size());
for (Object o : rawCollection) {
result.add(clazz.cast(o));
}
return result;
}
测试方法:
@Test(expected = ClassCastException.class)
public void givenRawListWithWrongType_whenAssignToTypedListAfterCallingCastList2_shouldThrowException() {
List rawList = UncheckedConversion.getRawListWithMixedTypes();
UncheckedConversion.castList2(String.class, rawList);
}
6. 总结
- “unchecked conversion” 是一个编译时警告,表示类型转换未经检查。
- 它通常出现在将原始类型赋值给泛型类型时。
- 虽然程序可能正常运行,但存在运行时类型转换失败的风险。
- 最好的做法是使用泛型方法来避免警告。
- 如果无法修改源代码,可以使用
@SuppressWarnings("unchecked")
或手动类型检查来规避风险。
一如既往,本文中的所有代码都可以在 GitHub 上找到。