成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

面試官:說說什么是泛型的類型擦除?

開發 前端
本文由面試中常見的一道面試題入手,介紹了java中泛型的類型擦除相關知識,通過這一過程,也便于大家理解為什么平常總是說java中的泛型是一個偽泛型,同時也有助于大家認識到java中泛型的一些缺陷。

[[419148]]

先看一道常見的面試題,下面的代碼的執行結果是什么?

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

首先,我們知道getClas方法獲取的是對象運行時的類(Class),那么這個問題也就可以轉化為ArrayList<String>t和ArrayList<Integer>的對象在運行時對應的Class是否相同?

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

  1. class java.util.ArrayList 

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

那么為什么要進行泛型的類型擦除呢?查閱的一些資料中,解釋說類型擦除的主要目的是避免過多的創建類而造成的運行時的過度消耗。試想一下,如果用List<A>表示一個類型,再用List<B>表示另一個類型,以此類推,無疑會引起類型的數量爆炸。

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

類型擦除做了什么?

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

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

  1. jad -sjava Test.class  

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

1、無限制類型擦除

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

2、有限制類型擦除

當類定義中的類型參數存在限制時,在類型擦除中替換為類型參數的上界或者下界。下面的代碼中,經過擦除后T被替換成了Integer:

3、擦除方法中的類型參數

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

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

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

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

執行結果如下:

  1. [E] 

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

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

最終也只能夠獲取到:

  1. [K, V] 

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

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

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

當我們嘗試將User類型的對象放入String類型的數組時,泛型的約束會在編譯期間就進行報錯,提示提供的User類型對象不適用于String類型數組。那么既然編譯時不行,那么我們就在運行時寫入,借助真正運行的class是沒有泛型約束這一特性,使用反射在運行時寫入:

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

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

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

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

再次執行代碼,程序運行到最后的打印語句時,報錯如下:

異常提示User類型的對象無法被轉換成String類型,這是否也就意味著,在取出對象時存在強制類型轉換呢?我們來看一下ArrayList中get方法的源碼:

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

可以看到,在取出元素時,會將這個元素強制類型轉換成泛型中的類型,也就是說在上面的代碼中,最后會嘗試強制把User對象轉換成String類型,在這一階段程序會報錯。通過這一過程,也再次證明了泛型可以對類型安全進行檢測。

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

下面我們看一個稍微有點復雜的例子,首先聲明一個接口,然后創建一個實現該接口的類:

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

按照之前我們的理解,在進行類型擦除后,應該是這樣的:

  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.     } 

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

可以看到,編譯后的代碼中生成了兩個get方法。參數為Object的get方法負責實現Fruit接口中的同名方法,然后在實現類中又額外添加了一個參數為Integer的get方法,這個方法也就是理論上應該生成的帶參數類型的方法。最終用接口方法調用額外添加的方法,通過這種方式構建了接口和實現類的關系,類似于起到了橋接的作用,因此也被稱為橋接方法,最終,通過這種機制保證了泛型情況下的Java多態性。

總結

本文由面試中常見的一道面試題入手,介紹了java中泛型的類型擦除相關知識,通過這一過程,也便于大家理解為什么平常總是說java中的泛型是一個偽泛型,同時也有助于大家認識到java中泛型的一些缺陷。了解類型擦除的原因以及原理,相信能夠方便大家在日常的工作中更好的使用泛型。

【編輯推薦】

 

責任編輯:姜華 來源: 碼農參上
相關推薦

2021-04-19 18:56:58

大數字符串運算

2020-07-22 08:05:44

中間人攻擊

2021-09-08 07:49:34

TypeScript 泛型場景

2025-04-01 00:00:00

項目CRUD單例模式

2023-12-19 09:24:22

LinuxBIOSUEFI

2023-12-27 18:16:39

MVCC隔離級別幻讀

2025-04-08 00:00:00

@AsyncSpring異步

2024-11-19 15:13:02

2025-04-16 00:00:01

JWT客戶端存儲加密令

2024-03-05 10:33:39

AOPSpring編程

2024-08-22 10:39:50

@Async注解代理

2024-05-30 08:04:20

Netty核心組件架構

2021-09-07 10:44:33

Java 注解開發

2024-06-07 10:05:31

2024-02-20 08:13:35

類加載引用Class

2024-07-31 08:28:37

DMAIOMMap

2024-12-06 07:00:00

2024-03-14 14:56:22

反射Java數據庫連接

2021-11-25 10:18:42

RESTfulJava互聯網

2024-03-11 18:18:58

項目Spring線程池
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产免费视频 | 2019天天干夜夜操 | 天天射视频 | 一区二区三区av | 亚洲精品99| 国产激情精品视频 | 亚洲视频中文字幕 | 精品久久久久久久久久久久久久 | 最新高清无码专区 | 国产午夜精品一区二区三区嫩草 | 欧美黄色网 | 亚洲精品18 | 国产精品婷婷 | 欧美1页 | 成人免费观看视频 | 亚洲天堂日韩精品 | 国产精品久久久久久影视 | 亚洲天堂中文字幕 | 日韩91 | 午夜精品一区二区三区免费视频 | 亚洲视频自拍 | 日韩中出 | 日韩高清一区二区 | 成人二区 | 求个av网址 | 午夜电影福利 | 中文字幕亚洲精品 | 久综合 | 亚洲精品粉嫩美女一区 | 最新av在线网址 | 中文字幕精品一区二区三区精品 | 亚洲一区二区在线 | www九色| 91精品久久久久久久久中文字幕 | 日日干日日 | 亚洲在线一区二区三区 | 91青娱乐在线| www.久久.com| 国产一区2区| 久久久久se | 日韩在线一区二区三区 |