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上找到