日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
面試官:說說什么是泛型的類型擦除?

先看一道常見的面試題,下面的代碼的執(zhí)行結(jié)果是什么?

創(chuàng)新互聯(lián)公司是一家專注于做網(wǎng)站、成都做網(wǎng)站與策劃設(shè)計(jì),龍井網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)公司做網(wǎng)站,專注于網(wǎng)站建設(shè)10年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:龍井等地區(qū)。龍井做網(wǎng)站價(jià)格咨詢:18982081108

 
 
 
 
  1. public static void main(String[] args) {
  2.     List list1=new ArrayList();
  3.     List list2=new ArrayList();
  4.     System.out.println(list1.getClass()==list2.getClass());
  5. }

首先,我們知道getClas方法獲取的是對象運(yùn)行時(shí)的類(Class),那么這個(gè)問題也就可以轉(zhuǎn)化為ArrayListt 和ArrayList 的對象在運(yùn)行時(shí)對應(yīng)的Class是否相同?

我們直接揭曉答案,運(yùn)行上面的代碼,程序會打印true,說明雖然在代碼中聲明了具體的泛型,但是兩個(gè)List對象對應(yīng)的Class是一樣的,對它們的類型進(jìn)行打印,結(jié)果都是:

 
 
 
 
  1. class java.util.ArrayList

也就是說,雖然ArrayList 和ArrayList 在編譯時(shí)是不同的類型,但是在編譯完成后都被編譯器簡化成了ArrayList,這一現(xiàn)象,被稱為泛型的類型擦除(Type Erasure)。泛型的本質(zhì)是參數(shù)化類型,而類型擦除使得類型參數(shù)只存在于編譯期,在運(yùn)行時(shí),jvm是并不知道泛型的存在的。

那么為什么要進(jìn)行泛型的類型擦除呢?查閱的一些資料中,解釋說類型擦除的主要目的是避免過多的創(chuàng)建類而造成的運(yùn)行時(shí)的過度消耗。試想一下,如果用List表示一個(gè)類型,再用List表示另一個(gè)類型,以此類推,無疑會引起類型的數(shù)量爆炸。

在對類型擦除有了一個(gè)大致的了解后,我們再看看下面的幾個(gè)問題。

類型擦除做了什么?

上面我們說了,編譯完成后會對泛型進(jìn)行類型擦除,如果想要眼見為實(shí),實(shí)際看一下的話應(yīng)該怎么辦呢?那么就需要對編譯后的字節(jié)碼文件進(jìn)行反編譯了,這里使用一個(gè)輕量級的小工具Jad來進(jìn)行反編譯(可以從這個(gè)地址進(jìn)行下載:https://varaneckas.com/jad/)

Jad的使用也很簡單,下載解壓后,把需要反編譯的字節(jié)碼文件放在目錄下,然后在命令行里執(zhí)行下面的命令就可以在同目錄下生成反編譯后的.java文件了:

 
 
 
 
  1. jad -sjava Test.class 

好了,工具準(zhǔn)備好了,下面我們就看一下不同情況下的類型擦除。

1、無限制類型擦除

當(dāng)類定義中的類型參數(shù)沒有任何限制時(shí),在類型擦除后,會被直接替換為Object。在下面的例子中, 中的類型參數(shù)T就全被替換為了Object(左側(cè)為編譯前的代碼,右側(cè)為通過字節(jié)碼文件反編譯得到的代碼):

2、有限制類型擦除

當(dāng)類定義中的類型參數(shù)存在限制時(shí),在類型擦除中替換為類型參數(shù)的上界或者下界。下面的代碼中,經(jīng)過擦除后T被替換成了Integer:

3、擦除方法中的類型參數(shù)

比較下面兩邊的代碼,可以看到在擦除方法中的類型參數(shù)時(shí),和擦除類定義中的類型參數(shù)一致,無限制時(shí)直接擦除為Object,有限制時(shí)則會被擦除為上界或下界:

反射能獲取泛型的類型嗎?

估計(jì)對Java反射比較熟悉小伙伴要有疑問了,反射中的getTypeParameters方法可以獲得類、數(shù)組、接口等實(shí)體的類型參數(shù),如果類型被擦除了,那么能獲取到什么呢?我們來嘗試一下使用反射來獲取類型參數(shù):

 
 
 
 
  1. System.out.println(Arrays.asList(list1.getClass().getTypeParameters()));

執(zhí)行結(jié)果如下:

 
 
 
 
  1. [E]

同樣,如果打印Map對象的參數(shù)類型:

 
 
 
 
  1. Map map=new HashMap<>();
  2. System.out.println(Arrays.asList(map.getClass().getTypeParameters()));

最終也只能夠獲取到:

 
 
 
 
  1. [K, V]

可以看到通過getTypeParameters方法只能獲取到泛型的參數(shù)占位符,而不能獲得代碼中真正的泛型類型。

能在指定類型的List中放入其他類型的對象嗎?

使用泛型的好處之一,就是在編譯的時(shí)候能夠檢查類型安全,但是通過上面的例子,我們知道運(yùn)行時(shí)是沒有泛型約束的,那么是不是就意味著,在運(yùn)行時(shí)可以把一個(gè)類型的對象能放進(jìn)另一類型的List呢?我們先看看正常情況下,直接調(diào)用add方法會有什么報(bào)錯(cuò):

當(dāng)我們嘗試將User類型的對象放入String類型的數(shù)組時(shí),泛型的約束會在編譯期間就進(jìn)行報(bào)錯(cuò),提示提供的User類型對象不適用于String類型數(shù)組。那么既然編譯時(shí)不行,那么我們就在運(yùn)行時(shí)寫入,借助真正運(yùn)行的class是沒有泛型約束這一特性,使用反射在運(yùn)行時(shí)寫入:

 
 
 
 
  1. public class ReflectTest {
  2.     static List list = new ArrayList<>();
  3.     public static void main(String[] args) {
  4.         list.add("1");
  5.         ReflectTest reflectTest =new ReflectTest();
  6.         try {
  7.             Field field = ReflectTest.class.getDeclaredField("list");
  8.             field.setAccessible(true);
  9.             List list=(List) field.get(reflectTest);
  10.             list.add(new User());
  11.         } catch (Exception e) {
  12.             e.printStackTrace();
  13.         }        
  14.     }
  15. }

執(zhí)行上面的代碼,不僅在編譯期間可以通過語法檢查,并且也可以正常地運(yùn)行,我們使用debug來看一下數(shù)組中的內(nèi)容:

可以看到雖然數(shù)組中聲明的泛型類型是String,但是仍然成功的放入了User類型的對象。那么,如果我們在代碼中嘗試取出這個(gè)User對象,程序還能正常執(zhí)行嗎,我們在上面代碼的最后再加上一句:

 
 
 
 
  1. System.out.println(list.get(1));

再次執(zhí)行代碼,程序運(yùn)行到最后的打印語句時(shí),報(bào)錯(cuò)如下:

異常提示User類型的對象無法被轉(zhuǎn)換成String類型,這是否也就意味著,在取出對象時(shí)存在強(qiáng)制類型轉(zhuǎn)換呢?我們來看一下ArrayList中g(shù)et方法的源碼:

 
 
 
 
  1. public E get(int index) {
  2.     rangeCheck(index);
  3.     return elementData(index);
  4. }
  5. E elementData(int index) {
  6.     return (E) elementData[index];
  7. }

可以看到,在取出元素時(shí),會將這個(gè)元素強(qiáng)制類型轉(zhuǎn)換成泛型中的類型,也就是說在上面的代碼中,最后會嘗試強(qiáng)制把User對象轉(zhuǎn)換成String類型,在這一階段程序會報(bào)錯(cuò)。通過這一過程,也再次證明了泛型可以對類型安全進(jìn)行檢測。

類型擦除會引起什么問題?

下面我們看一個(gè)稍微有點(diǎn)復(fù)雜的例子,首先聲明一個(gè)接口,然后創(chuàng)建一個(gè)實(shí)現(xiàn)該接口的類:

 
 
 
 
  1. public interface Fruit {
  2.     T get(T param);
  3. }
  4. public class Apple implements Fruit {
  5.     @Override
  6.     public Integer get(Integer param) {
  7.         return param;
  8.     }
  9. }

按照之前我們的理解,在進(jìn)行類型擦除后,應(yīng)該是這樣的:

 
 
 
 
  1. public interface Fruit {
  2.     Object get(Object param);
  3. }
  4. public class Apple implements Fruit {
  5.     @Override
  6.     public Integer get(Integer param) {
  7.         return param;
  8.     }
  9. }

但是,如果真是這樣的話那么代碼是無法運(yùn)行的,因?yàn)殡m然Apple類中也有一個(gè)get方法,但是與接口中的方法參數(shù)不一致,也就是說沒有覆蓋接口中的方法。針對這種情況,編譯器會通過添加一個(gè)橋接方法來滿足語法上的要求,同時(shí)保證了基于泛型的多態(tài)能夠有效。我們反編譯上面代碼生成的字節(jié)碼文件:

可以看到,編譯后的代碼中生成了兩個(gè)get方法。參數(shù)為Object的get方法負(fù)責(zé)實(shí)現(xiàn)Fruit接口中的同名方法,然后在實(shí)現(xiàn)類中又額外添加了一個(gè)參數(shù)為Integer的get方法,這個(gè)方法也就是理論上應(yīng)該生成的帶參數(shù)類型的方法。最終用接口方法調(diào)用額外添加的方法,通過這種方式構(gòu)建了接口和實(shí)現(xiàn)類的關(guān)系,類似于起到了橋接的作用,因此也被稱為橋接方法,最終,通過這種機(jī)制保證了泛型情況下的Java多態(tài)性。

總結(jié)

本文由面試中常見的一道面試題入手,介紹了java中泛型的類型擦除相關(guān)知識,通過這一過程,也便于大家理解為什么平??偸钦fjava中的泛型是一個(gè)偽泛型,同時(shí)也有助于大家認(rèn)識到j(luò)ava中泛型的一些缺陷。了解類型擦除的原因以及原理,相信能夠方便大家在日常的工作中更好的使用泛型。


新聞名稱:面試官:說說什么是泛型的類型擦除?
瀏覽路徑:
http://www.dlmjj.cn/article/dhpidic.html