1.概述
在这个简短的教程中,我们将集中讨论ClassCastException
,这是一个常见的Java异常。
ClassCastException
是未经检查的异常,它指示代码已尝试将引用转换为不是子类型的类型。
让我们看一下导致引发此异常的一些情况,以及如何避免它们。
2.显式投射
对于我们的下一个实验,让我们考虑以下类:
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。选修课
ClassCastException
的最常见情况是显式转换为不兼容的类型。
例如,让我们尝试将Frog
放到Mammal
:
Frog frog = new Frog();
Mammal mammal = (Mammal) frog;
我们可能会在ClassCastException
,但实际上,我们会收到编译错误:“不兼容的类型:无法将Frog转换为Mammal”。但是,当我们使用普通的超类型时,情况就发生了变化:
Animal animal = new Frog();
Mammal mammal = (Mammal) animal;
现在,我们从第二行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)
由于Frog
Mammal
的子类型, Frog
Mammal
的检查后向下转换是不兼容的。在这种情况下,编译器无法为我们提供帮助,因为Animal
变量可能包含兼容类型的引用。
有趣的是,仅当我们尝试强制转换为明确不兼容的类时,才会发生编译错误。接口并非如此,因为Java支持多个接口继承,但仅支持类的单个继承。因此,编译器无法确定引用类型是否实现特定的接口。让我们举例说明:
Animal animal = new Frog();
Serializable serial = (Serializable) animal;
我们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。转换数组
我们已经看到了类如何处理转换,现在让我们看一下数组。数组强制转换与类强制转换相同。但是,我们可能会因自动装箱和键入促销而感到困惑,或者缺少它们。
因此,让我们看看尝试以下转换时原始数组会发生什么:
Object primitives = new int[1];
Integer[] integers = (Integer[]) primitives;
第二行抛出ClassCastException
因为自动装箱不适用于数组。
如何进行类型推广?让我们尝试以下方法:
Object primitives = new int[1];
long[] longs = (long[]) primitives;
我们还得到了ClassCastException
因为类型提升不适用于整个数组。
2.3。安全铸造
在显式转换的情况下,强烈建议在尝试使用instanceof
进行转换之前检查类型的兼容性**。**
让我们看一个安全的强制转换示例:
Mammal mammal;
if (animal instanceof Mammal) {
mammal = (Mammal) animal;
} else {
// handle exceptional case
}
3.堆污染
按照Java规范:“仅当程序执行某些涉及原始类型的操作,这会引起编译时未经检查的警告时,才会发生堆污染”。
对于我们的实验,让我们考虑以下通用类:
public static class Box<T> {
private T content;
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
}
现在,我们将尝试如下污染堆:
Box<Long> originalBox = new Box<>();
Box raw = originalBox;
raw.setContent(2.5);
Box<Long> bound = (Box<Long>) raw;
Long content = bound.getContent();
最后一行将抛出ClassCastException
因为它无法将D ouble
引用Long
。
4.通用类型
在Java中使用泛型时,我们必须警惕类型擦除,这在某些情况下ClassCastException
让我们考虑以下通用方法:
public static <T> T convertInstanceOfObject(Object o) {
try {
return (T) o;
} catch (ClassCastException e) {
return null;
}
}
现在让我们称之为:
String shouldBeNull = convertInstanceOfObject(123);
乍一看,我们可以合理地期望catch块返回一个空引用。但是,在运行时,由于类型擦除,参数将强制转换为Object
而不是String
。因此,编译器面临将Integer
分配给String
的任务,这将引发ClassCastException.
5.结论
在本文中,我们研究了一系列不适当转换的常见情况。
不管是隐式还是显式,将Java引用强制转换为另一种类型都可能导致ClassCastException
除非目标类型与实际类型相同或其后代。
本文中使用的代码可以 在GitHub上找到。
0 评论