Explanation of ClassCastException in Java – Java中ClassCastException的解释

最后修改: 2020年 11月 19日

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

1. Overview

1.概述

In this short tutorial, we’ll focus on ClassCastException, a common Java exception.

在这个简短的教程中,我们将关注ClassCastException,一个常见的Java异常

ClassCastException is an unchecked exception that signals the code has attempted to cast a reference to a type of which it’s not a subtype.

ClassCastException是一个未经检查的异常,它表明代码试图将一个引用投向一个它不属于的子类型

Let’s look at some scenarios that lead to this exception being thrown and how we can avoid them.

让我们看看导致这种异常被抛出的一些情况以及我们如何避免它们。

2. Explicit Casting

2.明确铸造

For our next experiments, let’s consider the following classes:

对于我们接下来的实验,让我们考虑以下的类。

public interface Animal {
    String getName();
}
public class Mammal implements Animal {
    @Override
    public String getName() {
        return "Mammal";
    }
}
public class Amphibian implements Animal {
    @Override
    public String getName() {
        return "Amphibian";
    }
}
public class Frog extends Amphibian {
    @Override
    public String getName() {
        return super.getName() + ": Frog";
    }
}

2.1. Casting Classes

2.1.铸造类

By far, the most common scenario for encountering a ClassCastException is explicitly casting to an incompatible type.

到目前为止,遇到ClassCastException的最常见情况是明确地将其转换为一个不兼容的类型。

For example, let’s try to cast a Frog to a Mammal:

例如,让我们尝试将一只青蛙投给一只哺乳动物

Frog frog = new Frog();
Mammal mammal = (Mammal) frog;

We might expect a ClassCastException here, but in fact, we get a compilation error: “incompatible types: Frog cannot be converted to Mammal”. However, the situation changes when we use the common super-type:

我们可能以为这里会出现ClassCastException,但事实上,我们得到的是一个编译错误。”不兼容的类型。然而,当我们使用常见的超级类型时,情况就发生了变化。

Animal animal = new Frog();
Mammal mammal = (Mammal) animal;

Now, we get a ClassCastException from the second line:

现在,我们从第二行得到一个ClassCastException

Exception in thread "main" java.lang.ClassCastException: class Frog cannot be cast to class Mammal (Frog and Mammal are in unnamed module of loader 'app') 
at Main.main(Main.java:9)

A checked downcast to Mammal is incompatible from a Frog reference because Frog is not a subtype of Mammal. In this case, the compiler cannot help us, as the Animal variable may hold a reference of a compatible type.

Mammal的检查下移与Frog引用不兼容,因为Frog不是Mammal的子类型。在这种情况下,编译器无法帮助我们,因为Animal变量可能持有一个兼容类型的引用。

It’s interesting to note that the compilation error only occurs when we attempt to cast to an unequivocally incompatible class. The same is not true for interfaces because Java supports multiple interface inheritance, but only single inheritance for classes. Thus, the compiler can’t determine if the reference type implements a specific interface or not. Let’s exemplify:

值得注意的是,只有当我们试图投递到一个明确不兼容的类时,才会发生编译错误。对于接口来说,情况就不一样了,因为Java支持多接口继承,但对于类来说只支持单继承。因此,编译器无法确定引用类型是否实现了一个特定的接口。让我们来举例说明。

Animal animal = new Frog();
Serializable serial = (Serializable) animal;

We get a ClassCastException on the second line instead of a compilation error:

我们在第二行得到一个ClassCastException,而不是一个编译错误。

Exception in thread "main" java.lang.ClassCastException: class Frog cannot be cast to class java.io.Serializable (Frog is in unnamed module of loader 'app'; java.io.Serializable is in module java.base of loader 'bootstrap') 
at Main.main(Main.java:11)

2.2. Casting Arrays

2.2.铸造数组

We’ve seen how classes handle casting, now let’s look at arrays. Array casting works the same as class casting. However, we might get confused by autoboxing and type-promotion, or lack thereof.

我们已经看到了类是如何处理铸造的,现在我们来看看数组。数组投射的工作原理与类的投射相同。然而,我们可能会被自动排版和类型推广所迷惑,或者说没有自动排版。

Thus, let’s see what happens for primitive arrays when we attempt the following cast:

因此,让我们看看当我们试图进行以下投射时,原始数组会发生什么。

Object primitives = new int[1];
Integer[] integers = (Integer[]) primitives;

The second line throws a ClassCastException as autoboxing doesn’t work for arrays.

第二行抛出一个ClassCastException,因为autoboxing对数组不起作用。

How about type promotion? Let’s try the following:

类型推广如何?让我们试试下面的方法。

Object primitives = new int[1];
long[] longs = (long[]) primitives;

We also get a ClassCastException because the type promotion doesn’t work for entire arrays.

我们还得到一个ClassCastException,因为类型推广对整个数组不起作用。

2.3. Safe Casting

2.3.安全浇注

In the case of explicit casting, it is highly recommended to check the compatibility of the types before attempting to cast using instanceof.

在显式铸造的情况下,强烈建议在尝试铸造之前检查类型的兼容性使用instanceof

Let’s look at a safe cast example:

我们来看看一个安全投掷的例子。

Mammal mammal;
if (animal instanceof Mammal) {
    mammal = (Mammal) animal;
} else {
    // handle exceptional case
}

3. Heap Pollution

3.垃圾堆污染

As per the Java Specification: “Heap pollution can only occur if the program performed some operation involving a raw type that would give rise to a compile-time unchecked warning”.

根据Java规范:”堆污染只有在程序执行了一些涉及原始类型的操作,会引起编译时未检查的警告时,才会发生。”

For our experiment, let’s consider the following generic class:

在我们的实验中,让我们考虑下面这个通用类。

public static class Box<T> {
    private T content;

    public T getContent() {
        return content;
    }

    public void setContent(T content) {
        this.content = content;
    }
}

We will now attempt to pollute the heap as follows:

我们现在将尝试对堆进行如下的污染。

Box<Long> originalBox = new Box<>();
Box raw = originalBox;
raw.setContent(2.5);
Box<Long> bound = (Box<Long>) raw;
Long content = bound.getContent();

The last line will throw a ClassCastException as it cannot transform a Double reference to Long.

最后一行将抛出一个ClassCastException,因为它不能将一个Double引用转换为Long

4. Generic Types

4.通用类型

When using generics in Java, we must be wary of type erasure, which can lead to ClassCastException as well in some conditions.

在Java中使用泛型时,我们必须警惕type erasure,它在某些条件下也会导致ClassCastException

Let’s consider the following generic method:

让我们考虑以下的通用方法。

public static <T> T convertInstanceOfObject(Object o) {
    try {
        return (T) o;
    } catch (ClassCastException e) {
        return null;
    }
}

And now let’s call it:

现在让我们称它为

String shouldBeNull = convertInstanceOfObject(123);

At first look, we can reasonably expect a null reference returned from the catch block. However, at runtime, due to type erasure, the parameter is cast to Object instead of String. Thus the compiler is faced with the task of assigning an Integer to String, which throws ClassCastException.

乍一看,我们可以合理地期望从catch块返回一个空引用。然而,在运行时,由于类型擦除,参数被转换为Object而不是String。因此,编译器面临着将Integer分配给String的任务,这就引发了ClassCastException。

5. Conclusion

5.总结

In this article, we have looked at a series of common scenarios for inappropriate casting.

在这篇文章中,我们已经看了一系列不恰当的铸造的常见情况。

Whether implicit or explicit, casting Java references to another type can lead to ClassCastException unless the target type is the same or a descendent of the actual type.

无论是隐式还是显式,将Java引用转为另一类型会导致ClassCastException,除非目标类型与实际类型相同或为后者的后代

The code used in this article can be found over on GitHub.

本文中使用的代码可以在GitHub上找到over