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

Java的泛型擦除和運(yùn)行時(shí)泛型信息獲取方式

 更新時(shí)間:2024年09月19日 10:24:13   作者:wating1112  
Java泛型在編譯時(shí)會(huì)發(fā)生類型擦除,即泛型參數(shù)被替換為它們的限定類型(如Object),這使得ArrayList<Integer>和ArrayList<String>在運(yùn)行時(shí)類型相同,盡管如此,我們可以通過(guò)定義類或匿名內(nèi)部類的方式在運(yùn)行時(shí)獲取泛型信息

Java 的泛型擦除和泛型信息獲取

Java 的泛型擦除

擦除

Class c1 = new ArrayList<Integer>().getClass();  
Class c2 = new ArrayList<String>().getClass();   
System.out.println(c1 == c2);  
  
/* Output 
true 
*/  

ArrayList<Integer> 和 ArrayList<String> 在編譯的時(shí)候是完全不同的類型。你無(wú)法在寫(xiě)代碼時(shí),把一個(gè) String 類型的實(shí)例加到 ArrayList<Integer> 中。但是在程序運(yùn)行時(shí),的的確確會(huì)輸出true。

這就是 Java 泛型的類型擦除造成的,因?yàn)椴还苁?ArrayList<Integer> 還是 ArrayList<String>,在編譯時(shí)都會(huì)被編譯器擦除成了 ArrayList。Java 之所以要避免在創(chuàng)建泛型實(shí)例時(shí)而創(chuàng)建新的類,從而避免運(yùn)行時(shí)的過(guò)度消耗。

List<Integer> list = new ArrayList<Integer>();  
Map<Integer, String> map = new HashMap<Integer, String>();  
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));  
System.out.println(Arrays.toString(map.getClass().getTypeParameters()));  
  
/* Output 
[E] 
[K, V] 
*/  

我們可能期望能夠獲得真實(shí)的泛型參數(shù),但是僅僅獲得了聲明時(shí)泛型參數(shù)占位符。getTypeParameters 方法的 Javadoc 也是這么解釋的:僅返回聲明時(shí)的泛型參數(shù)。所以,通過(guò) getTypeParamters 方法無(wú)法獲得運(yùn)行時(shí)的泛型信息。

擦除到上限

class A<T extends Number> {
}

再用 javap -v cc.unmi.A 來(lái)看泛型簽名

Signature: #18 // <T:Ljava/lang/Number;>Ljava/lang/Object;

轉(zhuǎn)換

本來(lái)a是帶有泛型信息,但是b沒(méi)有,所以在賦值過(guò)程中泛型信息就丟失了,b中的T的類型會(huì)變成其上限Number

//一下代碼中,當(dāng)把一個(gè)帶泛型信息的a的實(shí)例賦值給一個(gè)不帶泛型信息的的b的時(shí)候,
//a中所有的泛型信息都會(huì)發(fā)生丟失,也就是說(shuō)T是Integer的這一信息會(huì)丟失,b只知道
//T的類型是Number而已
package ErasureAndConversion;
 
 
import UseIt.A1;
 
class Apple<T extends  Number>{
    T size;
    public Apple(){
 
    }
    public Apple(T size){
        this.size = size;
    }
    public void setSize(T size){
        this.size = size;
    }
    public T getSize(){
        return this.size;
    }
}
 
 
public class Erasure {
    public static void main(String args[]){
        Apple<Integer> a = new Apple<>(6);
//        a指向的實(shí)例是帶有泛型信息的
 
        Integer as = a.getSize();
//        實(shí)例a中的T是Integer類型的,所以賦值給as沒(méi)有任何問(wèn)題
        Apple b  = a;
//        這一句就是說(shuō)明問(wèn)題的關(guān)鍵了,b是不帶泛型信息的,所以a中的泛型信息
//        也就會(huì)被擦除,所以,a中的T的類型就只是Number而已了
 
        Number size1 = b.getSize();
//        b中的T類型是Number,所以賦值給Number類型的值沒(méi)有任何問(wèn)題
 
 
//        Integer size2 = b.getSize();
//        但是,b中的T并不是Integer的了,因?yàn)榉盒托畔⒁呀?jīng)被擦除了,所以這一句會(huì)
//        報(bào)錯(cuò)。
        Integer size3 = a.getSize();
//       最后這一句并不會(huì)報(bào)錯(cuò),b會(huì)發(fā)生泛型信息丟失但是a并不會(huì)受影響
    }
}

下面這兩個(gè)例子說(shuō)明的是一樣的問(wèn)題,或者說(shuō)第二個(gè)例子是第一個(gè)例子的直觀表現(xiàn),說(shuō)的是當(dāng)把帶有泛型信息的集合賦值給沒(méi)有泛型信息的集合時(shí)泛型信息就丟失了,所以在把list賦值給ls的時(shí)候不會(huì)發(fā)生問(wèn)題,因?yàn)閘ist已經(jīng)不知道具體的泛型信息是什么了,所以是Object,所以可以賦值給ls,但是一旦要訪問(wèn)集合中的元素的時(shí)候,就會(huì)發(fā)生類型不匹配的問(wèn)題。

//java允許把一個(gè)List賦值給一個(gè)List<Type>所以在下面 List<String> ls = list;
//這一句僅僅只會(huì)發(fā)生警告而已。
package ErasureAndConversion;
 
import java.util.ArrayList;
import java.util.List;
 
public class Erasure2 {
    public static void main(String args[]){
        List<Integer> li = new ArrayList<>();
        li.add(6);
        li.add(5);
        List list = li;
        List<String> ls = list;
//        一樣的道理,List沒(méi)有泛型信息,所以li的泛型信息就丟失了,所以賦值給ls
//        是沒(méi)有問(wèn)題的
 
//        System.out.println(ls.get(0));
//        但是當(dāng)訪問(wèn)ls中的元素的時(shí)候,就會(huì)發(fā)生類型不匹配的問(wèn)題
    }
}
//這個(gè)例子和上面的例子是一模一樣的
package ErasureAndConversion;
 
import java.util.ArrayList;
import java.util.List;
 
public class Erasure3 {
    public static void main(String args[]){
        List list = new ArrayList();
        ((ArrayList) list).add(5);
        ((ArrayList) list).add(4);
//        System.out.println((String)list.get(0));
    }
}

泛型信息的獲取

繼承一個(gè)泛型基類

class A<T, ID> {  
}  
  
class B extends A<String, Integer> {  
}  
  
public class Generic {  
    public static void main(String[] args) {  
        ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericSuperclass();  
        
		Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
		for(Type actualTypeArgument: actualTypeArguments) {  
		    System.out.println(actualTypeArgument);  
		}  
		
		Class clazz = (Class) parameterizedType.getActualTypeArguments()[0];
        System.out.println(clazz);
    }  
}  

上面的代碼輸出:

class java.lang.String
class java.lang.Integer
class java.lang.String

實(shí)現(xiàn)一個(gè)泛型接口

interface A<T, ID> {  
}  
  
class B implements A<String, Integer> {  
}  

public class Generic {  
    public static void main(String[] args) {  
        ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericInterfaces();  
        
		Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
		for (Type actualTypeArgument : actualTypeArguments) {  
		    System.out.println(actualTypeArgument);  
		}  
		
		Class clazz = (Class) parameterizedType.getActualTypeArguments()[0];
        System.out.println(clazz);
    }  
}

同樣能得到上面的一樣的結(jié)果。

運(yùn)行時(shí)泛型信息的獲取 (假象,實(shí)質(zhì)是通過(guò)定義類的方式)

引入一點(diǎn)小知識(shí)

匿名內(nèi)部類:

  • 概念:即內(nèi)部類的簡(jiǎn)化寫(xiě)法
  • 前提:存在一個(gè)類(可以是具體類也可以是抽象類)或接口
  • 格式:new 類名或接口名{重寫(xiě)的方法}
  • 本質(zhì):創(chuàng)建的是繼承了類或?qū)崿F(xiàn)了接口的子類匿名對(duì)象

匿名類的聲明:

  • 匿名類的聲明是由java編譯器自動(dòng)派生自一個(gè)類實(shí)例創(chuàng)建表達(dá)式;
  • 匿名類永遠(yuǎn)不能是抽象的;
  • 匿名類總是隱式的final;
  • 匿名類總是一個(gè)內(nèi)部類,并且不能是static的;

由于Java泛型的實(shí)現(xiàn)機(jī)制,使用了泛型的代碼在運(yùn)行期間相關(guān)的泛型參數(shù)的類型會(huì)被擦除,我們無(wú)法在運(yùn)行期間獲知泛型參數(shù)的具體類型(所有的泛型類型在運(yùn)行時(shí)都是Object類型),但是在編譯java源代碼成 class文件中還是保存了泛型相關(guān)的信息,,這些信息被保存在class字節(jié)碼常量池中,使用了泛型的代碼處會(huì)生成一個(gè)signature簽名字段,通過(guò)簽名signature字段指明這個(gè)常量池的地址。

Java 引入泛型擦除的原因是避免因?yàn)橐敕盒投鴮?dǎo)致運(yùn)行時(shí)創(chuàng)建不必要的類。通過(guò)前面的知識(shí)我們其實(shí)就可以通過(guò)定義類的方式,在類信息中保留泛型信息,從而獲得這些泛型信息。簡(jiǎn)而言之,Java 的泛型擦除是有范圍的,即類定義中的泛型是不會(huì)被擦除的。

有些場(chǎng)景中,我們需要獲取泛型信息的。比如,在調(diào)用 HTTP 或 RPC 接口時(shí),我們需要進(jìn)行序列化和反序列的工作。

例如,我們通過(guò)一個(gè) HTTP 接口接收如下的 JSON 數(shù)據(jù)

[{  
    "name": "Stark",  
    "nickName": "Iron Man"  
},  
{  
    "name": "Rogers",  
    "nickName": "Captain America"  
}]  

我們需要將其映射為 List<Avenger>。

但是之前我們提到了泛型擦除,那我們所使用的 HTTP 或 RPC 框架是如何獲取 List 中的泛型信息呢?

如下代碼

Map<String, Integer> map = new HashMap<String, Integer>() {};  
Type type = map.getClass().getGenericSuperclass();  
ParameterizedType parameterizedType = ParameterizedType.class.cast(type);  
//ParameterizedType parameterizedType = (ParameterizedType)map.getClass().getGenericSuperclass();
for (Type typeArgument : parameterizedType.getActualTypeArguments()) {  
    System.out.println(typeArgument.getTypeName());  
}  
  
/* Output 
java.lang.String 
java.lang.Integer 
*/  

上面這段代碼展示了如何獲取 map 這個(gè)實(shí)例所對(duì)應(yīng)類的泛型信息。顯然,這次我們成功獲得了其中泛型參數(shù)信息。有了這些泛型參數(shù),上面所提到的序列化和反序列化工作就是可能的了。

那為什么之前不可以,而這次可以了呢?請(qǐng)注意一個(gè)細(xì)節(jié)

前面的變量聲明

Map<Integer, String> map = new HashMap<Integer, String>();  

本節(jié)中的變量聲明

Map<String, Integer> map = new HashMap<String, Integer>() {};  

其中最關(guān)鍵的差別是本節(jié)的變量聲明多了一對(duì)大括號(hào),其實(shí)是創(chuàng)建了一個(gè)匿名內(nèi)部類,這個(gè)類是 HashMap 的子類,泛型參數(shù)限定為了 String 和 Integer,這樣就通過(guò)定義類的方式保留了泛型信息。

框架中的應(yīng)用

其實(shí)很多框架就是使用類定義中的泛型不會(huì)被擦除這個(gè)特性,實(shí)現(xiàn)了相應(yīng)的功能。

例如,SpringWeb模塊的RestTemplate 和 alibaba的fastJson,我們可以使用如下寫(xiě)法:

//這里的ParameterizedTypeReference是一個(gè)抽象類,因此約束了必須創(chuàng)建ParameterizedTypeReference的子類,由此成功獲取到泛型的實(shí)際類型
ResponseEntity<ResponseDTO<UserKeyDTO>> result = restTemplate.exchange(url, null, new ParameterizedTypeReference<ResponseDTO<UserKeyDTO>>(){});

//通過(guò)創(chuàng)建TypeReference的匿名內(nèi)部類的方式來(lái)保留反省信息,以便json反序列化時(shí)能反射獲取到泛型實(shí)際類型
ResponseDTO<SysCryptDTO> responseDTO = JSONObject.parseObject(jsonString, new TypeReference<ResponseDTO<SysCryptDTO>>() {});

其中的 new ParameterizedTypeReference<YourType>() {} 就是通過(guò)定義一個(gè)匿名內(nèi)部類的方式來(lái)獲得泛型信息,從而進(jìn)行反序列化的工作。

總結(jié)

Java 泛型擦除是 Java 泛型中的一個(gè)重要特性,其目的是避免過(guò)多的創(chuàng)建類而造成的運(yùn)行時(shí)的過(guò)度消耗。所以,想 ArrayList<Integer> 和 ArrayList<String> 這兩個(gè)實(shí)例,其類實(shí)例是同一個(gè)。

但很多情況下我們又需要在運(yùn)行時(shí)獲得泛型信息,那我們可以通過(guò)定義類的方式(通常為匿名內(nèi)部類,因?yàn)槲覀儎?chuàng)建這個(gè)類只是為了獲得泛型信息)在運(yùn)行時(shí)獲得泛型參數(shù),從而滿足例如序列化、反序列化等工作的需要。

只要理解了 Java 引入泛型擦除的原因,也自然能理解如何在運(yùn)行時(shí)獲取泛型信息了。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • java實(shí)現(xiàn)多線程交替打印

    java實(shí)現(xiàn)多線程交替打印

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)多線程交替打印,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-11-11
  • Java協(xié)程編程之Loom

    Java協(xié)程編程之Loom

    這篇文章主要介紹了Java協(xié)程編程Loom的方法,需要的朋友請(qǐng)看下文
    2021-08-08
  • 通過(guò)Spring AOP實(shí)現(xiàn)異常捕捉機(jī)制

    通過(guò)Spring AOP實(shí)現(xiàn)異常捕捉機(jī)制

    在開(kāi)發(fā)過(guò)程中,異常處理是一個(gè)不可忽視的重要環(huán)節(jié),合理、優(yōu)雅地處理異常不僅能提高代碼的魯棒性,還能提升系統(tǒng)的用戶體驗(yàn),本文將介紹如何通過(guò)Spring AOP實(shí)現(xiàn)一個(gè)高效的異常捕捉機(jī)制,使得異常處理變得更加優(yōu)雅和統(tǒng)一,需要的朋友可以參考下
    2024-08-08
  • Java實(shí)現(xiàn)簡(jiǎn)易畫(huà)圖板

    Java實(shí)現(xiàn)簡(jiǎn)易畫(huà)圖板

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)易畫(huà)圖板,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • java調(diào)用ffmpeg實(shí)現(xiàn)視頻轉(zhuǎn)換的方法

    java調(diào)用ffmpeg實(shí)現(xiàn)視頻轉(zhuǎn)換的方法

    這篇文章主要介紹了java調(diào)用ffmpeg實(shí)現(xiàn)視頻轉(zhuǎn)換的方法,較為詳細(xì)分析了java視頻格式轉(zhuǎn)換所需要的步驟及具體實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2015-06-06
  • JAVA面向?qū)ο?封裝原理及實(shí)例解析

    JAVA面向?qū)ο?封裝原理及實(shí)例解析

    這篇文章主要介紹了JAVA面向?qū)ο?封裝原理及實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-02-02
  • SpringMVC中參數(shù)綁定問(wèn)題實(shí)例詳解

    SpringMVC中參數(shù)綁定問(wèn)題實(shí)例詳解

    springmvc是用來(lái)處理頁(yè)面的一些請(qǐng)求,然后將數(shù)據(jù)再通過(guò)視圖返回給用戶的,下面這篇文章主要給大家介紹了關(guān)于SpringMVC中參數(shù)綁定問(wèn)題的相關(guān)資料,需要的朋友可以參考下
    2022-04-04
  • Java Hibernate中的持久化類和實(shí)體類關(guān)系

    Java Hibernate中的持久化類和實(shí)體類關(guān)系

    Hibernate是一種Java對(duì)象關(guān)系映射框架,通過(guò)持久化類將Java對(duì)象映射到數(shù)據(jù)庫(kù)表中。持久化類需要實(shí)現(xiàn)無(wú)參構(gòu)造器、具有標(biāo)識(shí)屬性和使用注解或XML進(jìn)行映射。Hibernate通過(guò)Session來(lái)管理對(duì)象的狀態(tài),包括臨時(shí)狀態(tài)、持久化狀態(tài)和游離狀態(tài)
    2023-04-04
  • 詳解java中的深拷貝和淺拷貝(clone()方法的重寫(xiě)、使用序列化實(shí)現(xiàn)真正的深拷貝)

    詳解java中的深拷貝和淺拷貝(clone()方法的重寫(xiě)、使用序列化實(shí)現(xiàn)真正的深拷貝)

    這篇文章主要介紹了java中的深拷貝和淺拷貝(clone()方法的重寫(xiě)、使用序列化實(shí)現(xiàn)真正的深拷貝),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • Java算法之歸并排序舉例詳解

    Java算法之歸并排序舉例詳解

    這篇文章主要介紹了Java算法之歸并排序的相關(guān)資料,歸并排序是一種遞歸排序算法,通過(guò)將數(shù)組分成更小的子數(shù)組,遞歸地排序這些子數(shù)組,然后將它們合并成有序數(shù)組,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-04-04

最新評(píng)論

桐乡市| 出国| 宁蒗| 京山县| 荆门市| 永济市| 金溪县| 鲁山县| 垫江县| 昌都县| 西畴县| 镇安县| 彩票| 伊宁市| 南投市| 朝阳县| 淳安县| 和龙市| 科技| 克拉玛依市| 衡东县| 隆德县| 九龙县| 田林县| 乐清市| 秀山| 金川县| 泰顺县| 尖扎县| 北流市| 彰武县| 自贡市| 南汇区| 银川市| 胶州市| 宁武县| 洪江市| 高邮市| 阿拉善右旗| 隆昌县| 北京市|