最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入詳解Java泛型擦除原理與限制

 更新時(shí)間:2026年01月02日 10:53:56   作者:梵得兒SHI  
這篇文章主要介紹了深入詳解Java泛型擦除原理與限制,Java泛型的設(shè)計(jì)有個(gè)獨(dú)特之處,類型信息只存在于編譯期,運(yùn)行時(shí)會(huì)被徹底擦除,這種 擦除機(jī)制讓很多開發(fā)者困惑,下面我們來探索它,需要的朋友可以參考下

Java 泛型的設(shè)計(jì)有個(gè)獨(dú)特之處:類型信息只存在于編譯期,運(yùn)行時(shí)會(huì)被徹底擦除。這種 “擦除” 機(jī)制讓很多開發(fā)者困惑:為什么List<String>和List<Integer>在運(yùn)行時(shí)是同一個(gè)類型?為什么不能用基本類型作為泛型參數(shù)?為什么創(chuàng)建泛型數(shù)組會(huì)報(bào)錯(cuò)?今天我們就從泛型擦除的底層原理講起,徹底搞懂這些問題,看清泛型的 “真面目”。

一、泛型擦除-Java泛型的編譯期幻術(shù)

泛型是 Java 5 引入的特性,但為了兼容之前的版本(Java 5 之前沒有泛型),Java 采用了類型擦除(Type Erasure) 的實(shí)現(xiàn)方式:編譯時(shí)檢查泛型類型合法性,運(yùn)行時(shí)擦除所有泛型信息。也就是說,泛型只在編譯期起作用,運(yùn)行時(shí) JVM 根本不知道泛型參數(shù)的存在。

1. 擦除的核心過程-從泛型到原始類型

泛型擦除的本質(zhì)是將泛型類型替換為其原始類型(Raw Type),具體規(guī)則:

  • 若泛型參數(shù)有上限(如<T extends Number>),則擦除為該上限類型;
  • 若泛型參數(shù)無上限(如<T>),則擦除為Object;
  • 若有多個(gè)上限(如<T extends A & B>),則擦除為第一個(gè)上限類型。

示例:泛型類擦除前后對(duì)比

// 泛型類定義
public class Box<T extends Number> {
    private T value;
    public T getValue() { return value; }
    public void setValue(T value) { this.value = value; }
}
// 擦除后(編譯為字節(jié)碼的實(shí)際類型)
public class Box {  // 去掉泛型參數(shù)<T extends Number>
    private Number value;  // T被替換為上限Number
    public Number getValue() { return value; }  // 返回值類型變?yōu)镹umber
    public void setValue(Number value) { this.value = value; }  // 參數(shù)類型變?yōu)镹umber
}

2. 為什么需要擦除-兼容性妥協(xié)

Java 5 之前的代碼沒有泛型,大量使用原始類型(如List而非List<String>)。為了讓這些舊代碼能與新的泛型代碼無縫交互,Java 必須保證:泛型類在運(yùn)行時(shí)的類型與非泛型類兼容。例如,Java 5 之前的List和 Java 5 之后的List<String>,在運(yùn)行時(shí)必須是同一個(gè)類型(都是List.class),否則舊代碼無法操作新的泛型集合。擦除機(jī)制正是為了實(shí)現(xiàn)這種兼容性。

3. 擦除后的類型安全如何保證

擦除會(huì)移除泛型信息,那運(yùn)行時(shí)的類型安全怎么保證?答案是:編譯器在擦除的同時(shí),自動(dòng)添加類型檢查和轉(zhuǎn)型代碼。

// 泛型代碼
List<String> list = new ArrayList<>();
list.add("hello");
String str = list.get(0);
// 擦除后(編譯器生成的實(shí)際代碼)
List list = new ArrayList();
list.add("hello");  // 編譯時(shí)檢查:確保添加的是String
String str = (String) list.get(0);  // 自動(dòng)添加轉(zhuǎn)型代碼
  • 編譯期:檢查add("hello")是否符合List<String>的類型約束,若添加123會(huì)直接報(bào)錯(cuò);
  • 運(yùn)行期:通過自動(dòng)生成的(String)轉(zhuǎn)型代碼,保證取出的元素類型正確(若因特殊操作導(dǎo)致類型不匹配,仍會(huì)拋ClassCastException)。

4. 泛型擦除原理圖解

二、泛型擦除帶來的限制-這些操作為什么不允許

擦除機(jī)制雖然保證了兼容性,但也給泛型帶來了諸多限制。理解這些限制的根源,才能避免開發(fā)中的 “坑”。

限制1-不能用基本類型作為泛型參數(shù)

你可能注意到,List<int>會(huì)編譯報(bào)錯(cuò),必須用List<Integer>。這是因?yàn)椋悍盒筒脸髸?huì)替換為 Object 或上限類型,而基本類型(int、double 等)不是 Object 的子類,無法轉(zhuǎn)型。

  • 若聲明List<int>,擦除后應(yīng)為L(zhǎng)ist<Object>,但int是基本類型,不能直接存儲(chǔ)在Object數(shù)組中(需要裝箱為 Integer);
  • 編譯器為了避免這種矛盾,直接禁止基本類型作為泛型參數(shù),強(qiáng)制使用包裝類(Integer、Double 等)。

反例(編譯報(bào)錯(cuò)):

// 錯(cuò)誤:基本類型不能作為泛型參數(shù)
List<int> intList = new ArrayList<>();  // 編譯報(bào)錯(cuò)
Map<double, boolean> map = new HashMap<>();  // 編譯報(bào)錯(cuò)
// 正確:使用包裝類
List<Integer> intList = new ArrayList<>();
Map<Double, Boolean> map = new HashMap<>();

限制2-不能實(shí)例化泛型類型(new T())

無法在泛型類中直接創(chuàng)建泛型參數(shù)的實(shí)例(new T()),因?yàn)椴脸骉會(huì)被替換為Object或上限類型,編譯器無法確定具體類型。

反例(編譯報(bào)錯(cuò)):

public class Box<T> {
    public Box() {
        // 錯(cuò)誤:不能實(shí)例化泛型類型T
        T value = new T();  // 編譯報(bào)錯(cuò)
    }
}

原因:擦除后T變?yōu)镺bject,new T()會(huì)被視為new Object(),這顯然不符合預(yù)期(我們想要的是T的實(shí)例,而非 Object)。

解決方案:通過反射創(chuàng)建實(shí)例(需傳入 Class 對(duì)象):

public class Box<T> {
    private T value;
    // 傳入Class對(duì)象,通過反射創(chuàng)建實(shí)例
    public Box(Class<T> clazz) throws InstantiationException, IllegalAccessException {
        value = clazz.newInstance();  // 合法
    }
}
// 使用
Box<String> box = new Box<>(String.class);  // 需顯式傳入Class對(duì)象

限制3-不能創(chuàng)建泛型數(shù)組(new T[])

無法直接創(chuàng)建泛型數(shù)組(new T[10]),因?yàn)椴脸髷?shù)組的實(shí)際類型是Object[],會(huì)導(dǎo)致類型安全問題。

反例(編譯報(bào)錯(cuò)):

public class ArrayBox<T> {
    public void createArray() {
        // 錯(cuò)誤:不能創(chuàng)建泛型數(shù)組
        T[] array = new T[10];  // 編譯報(bào)錯(cuò)
    }
}

原因:擦除后T[]變?yōu)镺bject[],若將其賦值給具體類型的數(shù)組(如String[]),再存入其他類型元素,會(huì)在運(yùn)行時(shí)引發(fā)隱藏的ClassCastException:

// 假設(shè)允許創(chuàng)建T[],擦除后實(shí)際為Object[]
Object[] array = new Object[10];
String[] strArray = (String[]) array;  // 編譯不報(bào)錯(cuò)(危險(xiǎn)?。?
strArray[0] = 123;  // 運(yùn)行時(shí)拋ArrayStoreException(int不能存到String數(shù)組)

編譯器為了避免這種隱藏的風(fēng)險(xiǎn),直接禁止創(chuàng)建泛型數(shù)組。

解決方案:

  • 用ArrayList<T>代替泛型數(shù)組(推薦,無需處理類型問題);
  • 創(chuàng)建Object[]數(shù)組,使用時(shí)手動(dòng)轉(zhuǎn)型(需謹(jǐn)慎,可能引發(fā)異常):
public class ArrayBox<T> {
    private Object[] array;
    public ArrayBox(int size) {
        array = new Object[size];  // 創(chuàng)建Object數(shù)組
    }
    public T get(int index) {
        return (T) array[index];  // 取出時(shí)轉(zhuǎn)型
    }
    public void set(int index, T value) {
        array[index] = value;  // 存入時(shí)自動(dòng)裝箱
    }
}

限制4-不能用instanceof判斷泛型類型

instanceof是運(yùn)行時(shí)類型檢查,而泛型類型在運(yùn)行時(shí)已被擦除,因此無法用instanceof判斷泛型參數(shù)。

反例(編譯報(bào)錯(cuò)):

List<String> list = new ArrayList<>();
// 錯(cuò)誤:不能用instanceof判斷泛型類型
if (list instanceof List<String>) {  // 編譯報(bào)錯(cuò)
    // ...
}

原因:運(yùn)行時(shí)List<String>和List<Integer>都是List類型,instanceof無法區(qū)分。

替代方案:若需判斷集合元素類型,可通過泛型類的Class參數(shù)(需手動(dòng)傳入):

public class GenericChecker<T> {
    private Class<T> clazz;
    public GenericChecker(Class<T> clazz) {
        this.clazz = clazz;
    }
    // 檢查集合元素是否為T類型
    public boolean check(List<?> list) {
        for (Object obj : list) {
            if (!clazz.isInstance(obj)) {
                return false;
            }
        }
        return true;
    }
}
// 使用
GenericChecker<String> checker = new GenericChecker<>(String.class);
List<Object> list = Arrays.asList("a", "b", 123);
System.out.println(checker.check(list));  // false(包含Integer)

限制5-靜態(tài)變量/方法不能引用泛型類的類型參數(shù)

泛型類的類型參數(shù)是實(shí)例級(jí)別的(每個(gè)實(shí)例可以有不同的類型參數(shù)),而靜態(tài)成員是類級(jí)別的(所有實(shí)例共享),因此靜態(tài)變量 / 方法不能使用泛型類的類型參數(shù)。

反例(編譯報(bào)錯(cuò)):

原因:擦除后泛型類的類型參數(shù)消失,靜態(tài)成員無法關(guān)聯(lián)到具體的類型參數(shù)(不同實(shí)例的T可能不同)。

注意:靜態(tài)泛型方法是允許的,因?yàn)樗凶约旱姆盒蛥?shù)(獨(dú)立于類的類型參數(shù)):

public class StaticBox<T> {
    // 正確:靜態(tài)泛型方法有自己的類型參數(shù)S
    public static <S> S create(S obj) {
        return obj;
    }
}

泛型限制圖解

三、泛型擦除的后遺癥-橋接方法(Bridge Method)

擦除會(huì)導(dǎo)致一個(gè)隱藏問題:泛型類的方法重寫可能在擦除后變得不兼容。為了解決這個(gè)問題,編譯器會(huì)自動(dòng)生成橋接方法(Bridge Method)。

橋接方法的產(chǎn)生場(chǎng)景

假設(shè)有泛型父類和子類:

// 泛型父類
class Parent<T> {
    public void setValue(T value) {}
}
// 子類指定泛型參數(shù)為String
class Child extends Parent<String> {
    @Override
    public void setValue(String value) {}  // 重寫父類方法
}

擦除后,父類的setValue(T)變?yōu)閟etValue(Object),而子類的setValue(String)與父類的setValue(Object)參數(shù)類型不同(不滿足重寫條件)。這會(huì)導(dǎo)致多態(tài)失效:

Parent<String> parent = new Child();
parent.setValue("hello");  // 期望調(diào)用子類的setValue(String)

為了保證多態(tài)正確,編譯器會(huì)為子類自動(dòng)生成橋接方法:

class Child extends Parent {
    // 編譯器生成的橋接方法(重寫父類的setValue(Object))
    public void setValue(Object value) {
        setValue((String) value);  // 調(diào)用子類實(shí)際的setValue(String)
    }
    // 子類自己的方法
    public void setValue(String value) {}
}

橋接方法的作用是:在擦除后仍保持方法重寫的多態(tài)性,確保父類引用調(diào)用方法時(shí)能正確指向子類實(shí)現(xiàn)。

橋接方法驗(yàn)證

通過反射可以看到編譯器生成的橋接方法:

import java.lang.reflect.Method;
public class BridgeDemo {
    public static void main(String[] args) {
        for (Method method : Child.class.getMethods()) {
            if (method.getName().equals("setValue")) {
                System.out.println("方法:" + method);
                System.out.println("是否橋接方法:" + method.isBridge());
            }
        }
    }
}
// 輸出結(jié)果:
// 方法:public void Child.setValue(java.lang.String)
// 是否橋接方法:false
// 方法:public void Child.setValue(java.lang.Object)
// 是否橋接方法:true

可以清晰看到,子類有兩個(gè)setValue方法,其中setValue(Object)是橋接方法(isBridge()返回 true)。

四、總結(jié)-理解擦除用好泛型

泛型擦除是 Java 為了兼容性做出的妥協(xié),它既帶來了便利(兼容舊代碼),也帶來了限制(類型信息丟失)。核心要點(diǎn):

擦除原理:編譯時(shí)檢查泛型類型,運(yùn)行時(shí)將泛型參數(shù)替換為上限或 Object,同時(shí)自動(dòng)添加類型檢查和轉(zhuǎn)型代碼。

核心限制:

  • 不能用基本類型作為泛型參數(shù)(擦除后無法兼容 Object);
  • 不能實(shí)例化泛型類型(new T())和創(chuàng)建泛型數(shù)組(new T[]);
  • 不能用instanceof判斷泛型類型(運(yùn)行時(shí)無類型信息);
  • 靜態(tài)成員不能引用泛型類的類型參數(shù)(靜態(tài)與實(shí)例的級(jí)別沖突)。

橋接方法:編譯器自動(dòng)生成,用于解決擦除后方法重寫的多態(tài)性問題。

理解泛型擦除,不僅能避免開發(fā)中的常見錯(cuò)誤,更能讓你明白 Java 泛型的設(shè)計(jì)哲學(xué) —— 在兼容性和類型安全之間尋找平衡。雖然泛型有諸多限制,但合理使用(結(jié)合通配符、反射等)仍能寫出靈活且安全的代碼。記?。悍盒褪蔷幾g期的 “語法糖”,運(yùn)行時(shí)它的 “真面目” 是原始類型。

以上就是深入詳解Java泛型擦除原理與限制的詳細(xì)內(nèi)容,更多關(guān)于Java泛型擦除的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java?SpringBoot整合shiro-spring-boot-starterqi項(xiàng)目報(bào)錯(cuò)解決

    Java?SpringBoot整合shiro-spring-boot-starterqi項(xiàng)目報(bào)錯(cuò)解決

    這篇文章主要介紹了Java?SpringBoot整合shiro-spring-boot-starterqi項(xiàng)目報(bào)錯(cuò)解決,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考一下
    2022-08-08
  • SpringBoot?快速實(shí)現(xiàn)分庫(kù)分表的2種方式

    SpringBoot?快速實(shí)現(xiàn)分庫(kù)分表的2種方式

    本文將為您介紹?ShardingSphere?的一些基礎(chǔ)特性和架構(gòu)組成,以及在?Springboot?環(huán)境下通過JAVA編碼和Yml配置兩種方式快速實(shí)現(xiàn)分庫(kù)分表功能,感興趣的朋友跟隨小編一起看看吧
    2023-06-06
  • Maven項(xiàng)目中集成數(shù)據(jù)庫(kù)文檔生成工具的操作步驟

    Maven項(xiàng)目中集成數(shù)據(jù)庫(kù)文檔生成工具的操作步驟

    在?Maven?項(xiàng)目中,可以通過集成?數(shù)據(jù)庫(kù)文檔生成工具來自動(dòng)生成數(shù)據(jù)庫(kù)文檔,本文為大家整理了使用?screw-maven-plugin(推薦)的完整配置步驟,有需要的可以了解下
    2025-05-05
  • Java的中l(wèi)ombok下的@Builder注解用法詳解

    Java的中l(wèi)ombok下的@Builder注解用法詳解

    這篇文章主要介紹了Java的中l(wèi)ombok下的@Builder注解用法詳解,lombok注解在java進(jìn)行編譯時(shí)進(jìn)行代碼的構(gòu)建,對(duì)于java對(duì)象的創(chuàng)建工作它可以更優(yōu)雅,不需要寫多余的重復(fù)的代碼,在出現(xiàn)lombok之后,對(duì)象的創(chuàng)建工作更提供Builder方法,需要的朋友可以參考下
    2023-11-11
  • java中的除法運(yùn)算和取模運(yùn)算解讀

    java中的除法運(yùn)算和取模運(yùn)算解讀

    這篇文章主要介紹了java中的除法運(yùn)算和取模運(yùn)算,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • Java 方法簽名詳解及實(shí)例代碼

    Java 方法簽名詳解及實(shí)例代碼

    這篇文章主要介紹了 Java 方法簽名詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下
    2016-10-10
  • SpringBoot Shiro 權(quán)限注解不起作用的解決方法

    SpringBoot Shiro 權(quán)限注解不起作用的解決方法

    本文主要介紹了SpringBoot Shiro 權(quán)限注解不起作用的解決方法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • Java線程池配置的一些常見誤區(qū)總結(jié)

    Java線程池配置的一些常見誤區(qū)總結(jié)

    這篇文章主要給大家介紹了關(guān)于Java線程池配置的一些常見誤區(qū),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • springboot2.7報(bào)錯(cuò):request header is too large問題及解決

    springboot2.7報(bào)錯(cuò):request header is too la

    本文詳細(xì)介紹了在Spring Boot 2.7中使用@RequestParam傳參時(shí),請(qǐng)求頭大小超過限制導(dǎo)致報(bào)錯(cuò)的解決方案,包括調(diào)整配置文件、通過代碼配置和直接配置Tomcat等方法,并提醒注意事項(xiàng)
    2026-01-01
  • Spring @value用法示例詳解

    Spring @value用法示例詳解

    這篇文章主要介紹了Spring-@value用法詳解,為了簡(jiǎn)化讀取properties文件中的配置值,spring支持@value注解的方式來獲取,這種方式大大簡(jiǎn)化了項(xiàng)目配置,提高業(yè)務(wù)中的靈活性,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2022-08-08

最新評(píng)論

洪泽县| 高要市| 故城县| 肥东县| 长岛县| 建瓯市| 乌兰县| 府谷县| 绥阳县| 五华县| 怀集县| 托里县| 张家口市| 衢州市| 吉木乃县| 洛扎县| 泽普县| 阜南县| 德江县| 崇礼县| 孟津县| 荥经县| 庄浪县| 盘山县| 英吉沙县| 阿拉善左旗| 连州市| 广西| 江达县| 青岛市| 东乡| 项城市| 育儿| 绥宁县| 海晏县| 景德镇市| 陇川县| 黑山县| 巴林左旗| 禄丰县| 梨树县|