1.概述
有时,当我们编译Java源代码时,编译器可能会显示警告消息“unchecked conversion”
或“ The expression of type List needs unchecked conversion
。
在本教程中,我们将更深入地研究警告消息。我们将讨论此警告的含义,可能导致的问题以及如何解决潜在的问题。
2.启用Unchecked
警告选项
在研究“ unchecked conversion
”警告之前,我们要确保已启用用于打印此警告的Java编译器选项。
如果我们使用的是Eclipse JDT Compiler ,那么默认情况下会启用此警告。
当我们使用Oracle或OpenJDK javac编译器时,可以通过添加编译器选项-Xlint:unchecked.
来启用此警告-Xlint:unchecked.
通常,我们在IDE中编写和构建Java程序。我们可以在IDE的编译器设置中添加此选项。
例如,以下屏幕截图显示了如何在JetBrains IntelliJ中启用此警告:
Apache Maven是用于构建Java应用程序的广泛使用的工具。我们可以配置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>
既然我们已经确认Java编译器已启用此警告选项,那么让我们仔细看看该警告。
3.编译器何时会警告我们:“ unchecked conversion
”?
在上一节中,我们学习了如何通过设置Java编译器选项来启用警告。因此,不难想象**“unchecked conversion”
是编译时警告。通常,在将原始类型分配给参数化类型而不进行类型检查的情况下,我们会看到此警告。**
编译器允许此分配,因为编译器必须允许此分配来保持与不支持泛型的较旧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));
}
现在,如果我们编译上面的测试,我们将看到来自Java编译器的警告。
让我们使用Maven构建和测试我们的程序:
$ mvn clean test
...
[WARNING] .../UncheckedConversionDemoUnitTest.java:[12,66] unchecked conversion
required: java.util.List<java.lang.String>
found: java.util.List
...
[INFO] -------------------------------------------------------
[INFO] TESTS
[INFO] -------------------------------------------------------
...
[INFO] Tests run: 13, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
...
如上面的输出所示,我们已复制了编译器警告。
现实世界中的一个典型示例是我们使用Java Persistence API的[Query.getResultList()](https://docs.oracle.com/javaee/7/api/javax/persistence/Query.html#getResultList--)
方法。该方法返回原始类型List
对象。
但是,当我们尝试将原始类型列表分配给具有参数化类型的列表时,我们会在编译时看到以下警告:
List<MyEntity> results = entityManager.createNativeQuery("... SQL ...", MyEntity.class).getResultList();
此外,我们知道,如果编译器向我们发出警告,则意味着存在潜在风险。如果我们查看上面的Maven输出,我们将看到,尽管我们收到“ unchecked conversion
”警告,但是我们的测试方法可以正常工作。
自然,我们可能想问为什么编译器会用此消息警告我们,以及我们可能会遇到什么潜在问题?
接下来,让我们弄清楚。
4.为什么Java编译器会警告我们?
即使我们收到“ unchecked conversion
”警告,我们的测试方法也可以在上一节中很好地运行。这是因为getRawList()
方法仅将String
添加到返回列表中。
现在,让我们稍微改变一下方法:
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;
}
在新的getRawListWithMixedTypes()
方法中,我们将Date
对象添加到返回的列表中。由于我们返回的是可以包含任何类型的原始类型列表,因此允许。
接下来,让我们创建一个新的测试方法以调用getRawListWithMixedTypes()
方法并测试返回值:
@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
”警告,测试将通过。
这意味着当我们通过调用get(3)
获得Date
对象并将其类型转换为String.
时,抛出了ClassCastException
String.
在现实世界中,根据要求,有时会抛出异常为时已晚。
例如,我们分配List<String> strList = getRawListWithMixedTypes().
对于strList,
每个String
对象strList,
假设我们在相当复杂或昂贵的过程(例如外部API调用或事务性数据库操作)中使用它。
当我们在strList
中的某个元素上遇到ClassCastException
时,一些元素已被处理。因此, ClassCastException
来不及,可能会导致一些额外的还原或数据清除过程。
到目前为止,我们已经了解了“unchecked conversion”
警告背后的潜在风险。接下来,让我们看看如何避免这种风险。
5.我们该如何处理警告?
如果允许更改返回原始类型集合的方法,则应考虑将其转换为通用方法。这样,将确保类型安全。
但是,很可能当我们遇到“ unchecked conversion
”警告时,我们正在使用外部库中的方法。让我们看看在这种情况下我们可以做什么。
5.1。禁止警告
我们可以使用注释SuppressWarnings(“unchecked”)
取消警告。
但是,仅当我们确定类型转换是安全的时,才应使用@SuppressWarnings(“unchecked”)
批注,因为它仅抑制警告消息而无需任何类型检查。
让我们来看一个例子:
Query query = entityManager.createQuery("SELECT e.field1, e.field2, e.field3 FROM SomeEntity e");
@SuppressWarnings("unchecked")
List<Object[]> list = query.list();
如前所述,JPA的Query.getResultList()
方法返回原始的List
对象。根据查询,我们确定原始类型列表可以转换为List<Object[]>
。因此,我们可以在赋值语句上方添加@SuppressWarnings
,以禁止显示“ unchecked conversion
”警告。
5.2。在使用原始类型集合之前检查类型转换
警告消息“ unchecked conversion
”表示我们应在分配之前检查转换。
要检查类型转换,我们可以遍历原始类型集合并将每个元素都转换为我们的参数化类型。这样,如果有些元素的类型错误,我们可以在真正使用该元素之前获取ClassCastException
。
我们可以构建一个通用方法来进行类型转换。根据特定要求,我们可以以不同方式处理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;
}
让我们通过单元测试方法测试上面的castList()
方法:
@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.")));
}
当我们构建并执行测试方法时,“ unchecked conversion
”警告消失了,测试通过了。
当然,如果需要,我们可以更改castList()
方法以中断类型转换,并在检测到错误的类型后立即抛出ClassCastException
:
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;
}
与往常一样,让我们创建一个单元测试方法来测试castList2()
方法:
@Test(expected = ClassCastException.class)
public void givenRawListWithWrongType_whenAssignToTypedListAfterCallingCastList2_shouldThrowException() {
List rawList = UncheckedConversion.getRawListWithMixedTypes();
UncheckedConversion.castList2(String.class, rawList);
}
如果我们运行上述测试方法,它将通过。这意味着,一旦rawList
存在类型错误的元素, castList2()
方法将停止类型转换并抛出ClassCastException.
六,结论
在本文中,我们了解了“ unchecked conversion
”编译器警告是什么。此外,我们已经讨论了此警告的原因以及如何避免潜在风险。
0 评论