1. Overview
1.概述
Sometimes, when we compile our Java source, the compiler may print a warning message “unchecked conversion” or “The expression of type List needs unchecked conversion.”
有时,当我们编译我们的Java源代码时,编译器可能会打印一个警告信息“未检查的转换”或”List类型的表达式需要未检查的转换“。
In this tutorial, we’re going to take a deeper look at the warning message. We’ll discuss what this warning means, what problem it can lead to, and how to solve the potential problem.
在本教程中,我们将更深入地研究这个警告信息。我们将讨论这个警告意味着什么,它可能导致什么问题,以及如何解决这个潜在的问题。
2. Enabling the Unchecked Warning Option
2.启用未勾选的警告选项
Before we look into the “unchecked conversion” warning, let’s make sure that the Java compiler option to print this warning has been enabled.
在我们研究”未检查的转换“警告之前,让我们确保打印该警告的Java编译器选项已经被启用。
If we’re using the Eclipse JDT Compiler, this warning is enabled by default.
如果我们使用的是Eclipse JDT编译器,这个警告是默认启用的。
When we’re using the Oracle or OpenJDK javac compiler, we can enable this warning by adding the compiler option -Xlint:unchecked.
当我们使用Oracle或OpenJDK javac编译器时,我们可以通过添加编译器选项-Xlint:unchecked.来启用这个警告。
Usually, we write and build our Java program in an IDE. We can add this option in the IDE’s compiler settings.
通常情况下,我们在IDE中编写和构建我们的Java程序。我们可以在IDE的编译器设置中添加这个选项。
For example, the screenshot below shows how this warning is enabled in JetBrains IntelliJ:
例如,下面的截图显示了在JetBrains IntelliJ中如何启用该警告。
Apache Maven is a widely used tool for building Java applications. We can configure maven-compiler-plugin‘s compilerArguments to enable this option:
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>
Now that we’ve confirmed that our Java compiler has this warning option enabled, let’s take a closer look at this warning.
现在我们已经确认我们的Java编译器启用了这个警告选项,让我们仔细看看这个警告。
3. When Will the Compiler Warn Us: “unchecked conversion”?
3.什么时候编译器会向我们发出警告 “未检查的转换”?
In the previous section, we’ve learned how to enable the warning by setting the Java compiler option. Therefore, it’s not hard to imagine that “unchecked conversion” is a compile-time warning. Usually, we’ll see this warning when assigning a raw type to a parameterized type without type checking.
在上一节中,我们已经了解了如何通过设置Java编译器选项来启用该警告。因此,我们不难想象,“未检查的转换”是一个编译时警告。通常,我们会在将一个原始类型分配给一个没有类型检查的参数化类型时看到这个警告。
This assignment is allowed by the compiler because the compiler has to allow this assignment to preserve backward compatibility with older Java versions that do not support generics.
编译器允许这种赋值,因为编译器必须允许这种赋值,以保持与不支持泛型的旧Java版本的向后兼容性。
An example will explain it quickly. Let’s say we have a simple method to return a raw type List:
一个例子将快速解释它。假设我们有一个简单的方法来返回一个原始类型的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;
}
...
}
Next, let’s create a test method that calls the method and assigns the result to a variable with the type List<String>:
接下来,让我们创建一个测试方法,调用该方法并将结果分配给一个类型为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));
}
Now, if we compile our test above, we’ll see the warning from the Java compiler.
现在,如果我们编译上面的测试,我们会看到来自Java编译器的警告。
Let’s build and test our program using Maven:
让我们用Maven来构建和测试我们的程序。
$ mvn clean test
...
[WARNING] .../UncheckedConversionDemoUnitTest.java:[12,66] unchecked conversion
required: java.util.List<java.lang.String>
found: java.util.List
...
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
...
[INFO] Tests run: 13, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
...
As the output above shows, we’ve reproduced the compiler warning.
如上面的输出所示,我们已经重现了编译器警告。
A typical example in the real world is when we use Java Persistence API‘s Query.getResultList() method. The method returns a raw type List object.
现实世界中的一个典型例子是我们使用Java Persistence API的Query.getResultList()方法。该方法返回一个原始类型的List对象。
However, when we try to assign the raw type list to a list with a parameterized type, we’ll see this warning at compile-time:
然而,当我们试图将原始类型的 list 赋值给具有参数化类型的 list 时,我们会在编译时看到这个警告。
List<MyEntity> results = entityManager.createNativeQuery("... SQL ...", MyEntity.class).getResultList();
Moreover, we know that if the compiler warns us of something, it means there are potential risks. If we review the Maven output above, we’ll see that although we get the “unchecked conversion” warning, our test method works without any problem.
此外,我们知道,如果编译器对我们发出警告,就意味着存在潜在的风险。如果我们回顾一下上面的Maven输出,就会发现尽管我们得到了”未检查的转换“警告,但我们的测试方法却能顺利运行。
Naturally, we may want to ask why the compiler warns us with this message and what potential problem we might have?
自然,我们可能想问为什么编译器会用这个信息警告我们,我们可能有什么潜在问题?
Next, let’s figure it out.
接下来,让我们来弄清楚。
4. Why Does the Java Compiler Warn Us?
4.为什么Java编译器会向我们发出警告?
Our test method works well in the previous section, even if we get the “unchecked conversion” warning. This is because the getRawList() method only adds Strings into the returned list.
我们的测试方法在上一节中运行良好,即使我们得到”未检查的转换“警告。这是因为getRawList() 方法只在返回的列表中添加Strings。
Now, let’s change the method a little bit:
现在,让我们稍微改变一下方法。
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;
}
In the new getRawListWithMixedTypes() method, we add a Date object to the returned list. It’s allowed since we’re returning a raw type list that can contain any types.
在新的getRawListWithMixedTypes()方法中,我们将一个Date对象添加到返回的列表中。这是允许的,因为我们要返回一个可以包含任何类型的原始类型列表。
Next, let’s create a new test method to call the getRawListWithMixedTypes() method and test the return value:
接下来,让我们创建一个新的测试方法来调用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."));
}
If we run the test method above, we’ll see the “unchecked conversion” warning again, and the test will pass.
如果我们运行上面的测试方法,我们会再次看到”未检查的转换“警告,并且测试会通过。
This means a ClassCastException has been thrown when we get the Date object by calling get(3) and attempt to cast its type to String.
这意味着当我们通过调用get(3) 获得Date对象并试图将其类型转换为String.时,抛出了一个ClassCastException。
In the real world, depending on the requirements, sometimes the exception is thrown too late.
在现实世界中,根据需求的不同,有时异常被抛出得太晚了。。
For example, we assign List<String> strList = getRawListWithMixedTypes(). For each String object in strList, suppose that we use it in a pretty complex or expensive process such as external API calls or transactional database operations.
例如,我们指派List<String> strList = getRawListWithMixedTypes()。对于strList中的每个String对象,假设我们在一个相当复杂或昂贵的过程中使用它,如外部API调用或事务性数据库操作。
When we encounter the ClassCastException on an element in the strList, some elements have been processed. Thus, the ClassCastException comes too late and may lead to some extra restore or data cleanup processes.
当我们在strList中的一个元素上遇到ClassCastException时,一些元素已经被处理了。因此,ClassCastException来得太晚,可能会导致一些额外的恢复或数据清理过程。
So far, we’ve understood the potential risk behind the “unchecked conversion” warning. Next, let’s see what we can do to avoid the risk.
到目前为止,我们已经了解了“未检查的转换”警告背后的潜在风险。接下来,让我们看看我们可以做些什么来避免这种风险。
5. What Shall We Do With the Warning?
5.我们该如何对待这个警告?
If we’re allowed to change the method that returns raw type collections, we should consider converting it into a generic method. In this way, type safety will be ensured.
如果我们被允许改变返回原始类型集合的方法,我们应该考虑将其转换为一个通用方法。这样一来,类型安全将得到保证。
However, it’s likely that when we encounter the “unchecked conversion” warning, we’re working with a method from an external library. Let’s see what we can do in this case.
然而,当我们遇到”未检查的转换“警告时,很可能是我们在使用一个来自外部库的方法。让我们看看在这种情况下我们能做什么。
5.1. Suppressing the Warning
5.1.抑制警告
We can use the annotation SuppressWarnings(“unchecked”) to suppress the warning.
我们可以使用注解SuppressWarnings(“unchecked”)来压制该警告。
However, we should use the @SuppressWarnings(“unchecked”) annotation only if we’re sure the typecast is safe because it merely suppresses the warning message without any type checking.
然而,我们应该使用@SuppressWarnings(“unchecked”)注解,只有当我们确定类型转换是安全的,因为它只是抑制了警告信息而没有进行任何类型检查。
Let’s see an example:
让我们看一个例子。
Query query = entityManager.createQuery("SELECT e.field1, e.field2, e.field3 FROM SomeEntity e");
@SuppressWarnings("unchecked")
List<Object[]> list = query.list();
As we’ve mentioned earlier, JPA’s Query.getResultList() method returns a raw typed List object. Based on our query, we’re sure the raw type list can be cast to List<Object[]>. Therefore, we can add the @SuppressWarnings above the assignment statement to suppress the “unchecked conversion” warning.
正如我们前面提到的,JPA的Query.getResultList()方法返回一个原始类型的List对象。根据我们的查询,我们确信原始类型的列表可以被投到List<Object[]>。因此,我们可以在赋值语句上方添加@SuppressWarnings来抑制”unchecked conversion“警告。
5.2. Checking Type Conversion Before Using the Raw Type Collection
5.2.在使用原始类型集之前检查类型转换
The warning message “unchecked conversion” implies that we should check the conversion before the assignment.
警告信息”未检查的转换“意味着我们应该在赋值前检查转换。
To check the type conversion, we can go through the raw type collection and cast every element to our parameterized type. In this way, if there are some elements with the wrong types, we can get ClassCastException before we really use the element.
为了检查类型转换,我们可以通过原始类型集合,把每个元素都投到我们的参数化类型中。这样一来,如果有一些元素的类型不对,我们就可以在真正使用该元素之前得到ClassCastException。
We can build a generic method to do the type conversion. Depending on the specific requirement, we can handle ClassCastException in different ways.
我们可以建立一个通用方法来进行类型转换。根据具体的要求,我们可以用不同的方式处理ClassCastException。
First, let’s say we’ll filter out the elements that have the wrong types:
首先,我们说我们要过滤掉那些有错误类型的元素。
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;
}
Let’s test the castList() method above by a unit test method:
让我们通过一个单元测试方法来测试上面的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.")));
}
When we build and execute the test method, the “unchecked conversion” warning is gone, and the test passes.
当我们构建并执行测试方法时,”未检查的转换“警告消失了,测试通过。
Of course, if it’s required, we can change our castList() method to break out of the type conversion and throw ClassCastException immediately once a wrong type is detected:
当然,如果需要的话,我们可以改变我们的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;
}
As usual, let’s create a unit test method to test the castList2() method:
像往常一样,让我们创建一个单元测试方法来测试castList2()方法。
@Test(expected = ClassCastException.class)
public void givenRawListWithWrongType_whenAssignToTypedListAfterCallingCastList2_shouldThrowException() {
List rawList = UncheckedConversion.getRawListWithMixedTypes();
UncheckedConversion.castList2(String.class, rawList);
}
The test method above will pass if we give it a run. It means that once there’s an element with the wrong type in rawList, the castList2() method will stop the type conversion and throw ClassCastException.
如果我们给它运行,上面的测试方法将通过。这意味着一旦在rawList中出现类型错误的元素,castList2()方法将停止类型转换并抛出ClassCastException。
6. Conclusion
6.结语
In this article, we’ve learned what the “unchecked conversion” compiler warning is. Further, we’ve discussed the cause of this warning and how to avoid the potential risk.
在这篇文章中,我们已经了解了什么是”未检查的转换“编译器警告。此外,我们还讨论了这个警告的原因以及如何避免潜在的风险。
As always, the code in this write-up is all available over on GitHub.
一如既往,本篇文章中的代码都可以在GitHub上找到。