Java中ClassCastException的說明
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上找到。