Java Warning “Unchecked Cast” – Java 警告 “未检查的 Cast&#8221

最后修改: 2021年 2月 21日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Sometimes, when we compile our Java source files, we see “unchecked cast” warning messages printed by the Java compiler.

有时,当我们编译我们的Java源文件时,我们看到Java编译器打印的”unchecked cast“警告信息。

In this tutorial, we’re going to take a closer look at the warning message. We’ll discuss what this warning means, why we’re warned, and how to solve the problem.

在本教程中,我们要仔细研究一下这个警告信息。我们将讨论这个警告是什么意思,为什么会被警告,以及如何解决这个问题。

Some Java compilers suppress unchecked warnings by default.

一些Java编译器默认会抑制未选中的警告。

Let’s make sure we’ve enabled the compiler’s option to print “unchecked” warnings before we look into this “unchecked cast” warning.

让我们确定我们已经启用了编译器打印 “unchecked “警告的选项,然后我们再研究这个”unchecked cast“警告。

2. What Does the “unchecked cast” Warning Mean?

2.2.“未检查的铸造”警告是什么意思?

The “unchecked cast” is a compile-time warning. Simply put, we’ll see this warning when casting a raw type to a parameterized type without type checking.

unchecked cast“是一个编译时警告。简单地说,当把一个原始类型投递到一个参数化类型而不进行类型检查时,我们会看到这个警告

An example can explain it straightforwardly. Let’s say we have a simple method to return a raw type Map:

一个例子可以直截了当地解释它。假设我们有一个简单的方法来返回一个原始类型的Map

public class UncheckedCast {
    public static Map getRawMap() {
        Map rawMap = new HashMap();
        rawMap.put("date 1", LocalDate.of(2021, Month.FEBRUARY, 10));
        rawMap.put("date 2", LocalDate.of(1992, Month.AUGUST, 8));
        rawMap.put("date 3", LocalDate.of(1976, Month.NOVEMBER, 18));
        return rawMap;
    }
...
}

Now, let’s create a test method to call the method above method and cast the result to Map<String, LocalDate>:

现在,让我们创建一个测试方法来调用上面的方法,并将结果投给Map<String, LocalDate>

@Test
public void givenRawMap_whenCastToTypedMap_shouldHaveCompilerWarning() {
    Map<String, LocalDate> castFromRawMap = (Map<String, LocalDate>) UncheckedCast.getRawMap();
    Assert.assertEquals(3, castFromRawMap.size());
    Assert.assertEquals(castFromRawMap.get("date 2"), LocalDate.of(1992, Month.AUGUST, 8));
}

The compiler has to allow this cast to preserve backward compatibility with older Java versions that do not support generics.

编译器必须允许这种铸造,以保持与不支持泛型的旧版Java的向后兼容性。

But if we compile our Java sources, the compiler will print the warning message. Next, let’s compile and run our unit tests using Maven:

但如果我们编译我们的Java源代码,编译器会打印警告信息。接下来,让我们用Maven编译并运行我们的单元测试。

$ mvn clean test
...
[WARNING] .../src/test/java/com/baeldung/uncheckedcast/UncheckedCastUnitTest.java:[14,97] unchecked cast
  required: java.util.Map<java.lang.String,java.time.LocalDate>
  found:    java.util.Map
...
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
...
[INFO] Results:
[INFO] 
[INFO] Tests run: 16, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...

As the Maven output shows, we’ve reproduced the warning successfully.

如Maven输出所示,我们成功复制了该警告。

On the other hand, our test works without any problem even though we see the “unchecked cast” compiler warning.

另一方面,我们的测试工作没有任何问题,即使我们看到”unchecked cast“编译器警告。

We know the compiler won’t warn us without reason. There must be some potential problem when we see this warning.

我们知道编译器不会无故警告我们。当我们看到这个警告时,一定有一些潜在的问题。

Let’s figure it out.

我们来算算看。

3. Why Does the Java Compiler Warn Us?

3.为什么Java编译器会向我们发出警告?

Our test method works fine in the previous section, although we see the “unchecked cast” warning. That’s because when we were casting the raw type Map to Map<String, LocalDate>, the raw Map contains only <String, LocalDate> entries. That is to say, the typecasting is safe.

我们的测试方法在上一节中工作正常,尽管我们看到了”unchecked cast“警告。这是因为当我们将原始类型Map转换为Map<String, LocalDate>时,原始Map只包含<String, LocalDate>条目。这就是说,类型转换是安全的。

To analyze the potential problem, let’s change the getRawMap() method a little bit by adding one more entry into the raw type Map:

为了分析这个潜在的问题,让我们稍微改变一下getRawMap()方法,在原始类型Map中多加入一个条目。

public static Map getRawMapWithMixedTypes() {
    Map rawMap = new HashMap();
    rawMap.put("date 1", LocalDate.of(2021, Month.FEBRUARY, 10));
    rawMap.put("date 2", LocalDate.of(1992, Month.AUGUST, 8));
    rawMap.put("date 3", LocalDate.of(1976, Month.NOVEMBER, 18));
    rawMap.put("date 4", new Date());
    return rawMap;
}

This time, we added a new entry to the Map with type <String, Date> in the method above.

这一次,我们在上面的方法中给Map添加了一个新条目,类型为<String, Date>

Now, let’s write a new test method to call the getRawMapWithMixedTypes() method:

现在,让我们写一个新的测试方法来调用getRawMapWithMixedTypes()/em>方法。

@Test(expected = ClassCastException.class)
public void givenMixTypedRawMap_whenCastToTypedMap_shouldThrowClassCastException() {
    Map<String, LocalDate> castFromRawMap = (Map<String, LocalDate>) UncheckedCast.getRawMapWithMixedTypes();
    Assert.assertEquals(4, castFromRawMap.size());
    Assert.assertTrue(castFromRawMap.get("date 4").isAfter(castFromRawMap.get("date 3")));
}

If we compile and run the test, the “unchecked cast” warning message is printed again. Also, our test will pass.

如果我们编译并运行测试,”unchecked cast“警告信息又被打印出来。同时,我们的测试将通过。

However, since our test has the expected = ClassCastException.class argument, it means the test method has thrown a ClassCastException.

然而,由于我们的测试有expected = ClassCastException.class参数,这意味着测试方法抛出了ClassCastException

If we take a closer look at it, the ClassCastException isn’t thrown on the line of casting the raw type Map to Map<String, LocalDate> although the warning message points to this line. Instead, the exception occurs when we get data with the wrong type by the key: castFromRawMap.get(“date 4”). 

如果我们仔细看看,ClassCastException并不是在将原始类型Map投递到Map<String, LocalDate> 这一行抛出的,尽管警告信息指向这一行。相反,当我们通过键获得错误类型的数据时,异常发生了castFromRawMap.get(”date 4″)。

If we cast a raw type collection containing data with the wrong types to a parameterized type collection, the ClassCastException won’t be thrown until we load the data with the wrong type.

如果我们将一个包含有错误类型的数据的原始类型集合投到一个参数化的类型集合中,那么在我们加载有错误类型的数据之前,ClassCastException不会被抛出

Sometimes, we may get the exception too late.

有时,我们可能得到的例外情况太晚。

For instance, we get a raw type Map with many entries by calling our method, and then we cast it to a Map with parameterized type:

例如,我们通过调用我们的方法得到一个有许多条目的原始类型的Map,然后我们把它转换为一个有参数化类型的Map

(Map<String, LocalDate>) UncheckedCast.getRawMapWithMixedTypes()

For each entry in the Map, we need to send the LocalDate object to a remote API. Until the time we encounter the ClassCastException, it’s very likely that a lot of API calls have already been made. Depending on the requirement, some extra restore or data cleanup processes may be involved.

对于Map中的每个条目,我们需要将LocalDate对象发送到远程API。在我们遇到ClassCastException之前,很可能已经进行了大量的API调用。根据不同的要求,可能会涉及一些额外的恢复或数据清理过程。

It’ll be good if we can get the exception earlier so that we can decide how to handle the circumstance of entries with the wrong types.

如果我们能更早地得到异常,那就好了,这样我们就能决定如何处理类型错误的条目的情况了。

As we understand the potential problem behind the “unchecked cast” warning, let’s have a look at what we can do to solve the problem.

当我们理解了”unchecked cast“警告背后的潜在问题后,让我们看看我们能做些什么来解决这个问题。

4. What Should We Do With the Warning?

4.我们应该如何处理这个警告?

4.1. Avoid Using Raw Types

4.1.避免使用原始类型

Generics have been introduced since Java 5. If our Java environment supports generics, we should avoid using raw types. This is because using raw types will make us lose all the safety and expressiveness benefits of generics.

从Java 5开始,泛型就被引入了。如果我们的Java环境支持泛型,我们应该避免使用原始类型。这是因为使用原始类型将使我们失去泛型的所有安全和表达能力的好处。

Moreover, we should search the legacy code and refactor those raw type usages to generics.

此外,我们应该搜索遗留的代码,将那些原始类型的使用重构为泛型。

However, sometimes we have to work with some old libraries. Methods from those old external libraries may return raw type collections.

然而,有时我们不得不与一些旧的库一起工作。来自这些旧的外部库的方法可能会返回原始类型集合。

Calling those methods and casting to parameterized types will produce the “unchecked cast” compiler warning. But we don’t have control over an external library.

调用这些方法并将其转换为参数化类型将产生”未检查的转换“编译器警告。但我们无法控制外部库。

Next, let’s have a look at how to handle this case.

接下来,让我们看一下如何处理这种情况。

4.2. Suppress the “unchecked” Warning

4.2.抑制”未检查的“警告

If we can’t eliminate the “unchecked cast” warning and we’re sure that the code provoking the warning is typesafe, we can suppress the warning using the SuppressWarnings(“unchecked”) annotation.

如果我们不能消除”unchecked cast“警告,并且我们确定引起该警告的代码是类型安全的,我们可以使用SuppressWarnings(“unchecked”)注解来抑制该警告

When we use the @SuppressWarning(“unchecked”) annotation, we should always put it on the smallest scope possible.

当我们使用@SuppressWarning(“unchecked”)注解时,我们应该总是把它放在尽可能小的范围内。

Let’s have a look at the remove() method from the ArrayList class as an example:

让我们看一下ArrayList类中的remove()方法,作为一个例子。

public E remove(int index) {
    Objects.checkIndex(index, size);
    final Object[] es = elementData;
                                                              
    @SuppressWarnings("unchecked") E oldValue = (E) es[index];
    fastRemove(es, index);
                                                              
    return oldValue;
}

4.3. Doing Typesafe Check Before Using the Raw Type Collection

4.3.在使用原始类型集合之前做类型安全检查

As we’ve learned, the @SuppressWarning(“unchecked”) annotation merely suppresses the warning message without actually checking if the cast is typesafe.

正如我们所了解的,@SuppressWarning(“unchecked”)注解只是抑制了警告信息,而没有实际检查cast是否类型安全。

If we’re not sure if casting a raw type is typesafe, we should check the types before we really use the data so that we can get the ClassCastException earlier.

如果我们不确定铸造一个原始类型是否类型安全,我们应该在我们真正使用数据之前检查类型,这样我们就可以更早得到ClassCastException

5. Conclusion

5.总结

In this article, we’ve learned what an “unchecked cast” compiler warning means.

在这篇文章中,我们已经了解了”unchecked cast“编译器警告意味着什么。

Further, we’ve addressed the cause of this warning and how to solve the potential problem.

此外,我们已经解决了这个警告的原因以及如何解决这个潜在的问题。

As always, the code in this write-up is all available over on GitHub.

一如既往,本篇文章中的代码都可以在GitHub上找到