Java 的泛型擦除和運行時泛型信息獲取

前言

現在很多程序員都會在簡歷中寫上精通 Java。但究竟怎樣才算是精通 Java 呢?我覺得不僅要熟練掌握 Java 語法和 JDK 的使用,還需要對 Java 這門語言的各方面原理有深入的了解。除了像并發、JVM 等方面,以及軟引用、弱引用等高級知識以外,其實很多我們每天接觸到的 Java 特性里面也是另有乾坤。Java 5 引入的泛型便是其中之一。本文這里不談泛型的使用以及泛型方法、泛型類的定義,這些東西很多書和文章都講了。本文將介紹一下 Java 泛型的擦除和運行時泛型獲取這兩個看似矛盾的特性。

Java 的泛型擦除

程序員界有句流行的話,叫 talk is cheap, show me the code,所以話不多說,看代碼。

代碼一

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

/* Output
true
*/

ArrayList<Integer>ArrayList<String> 在編譯的時候是完全不同的類型。你無法在寫代碼時,把一個 String 類型的實例加到 ArrayList<Integer> 中。但是在程序運行時,的的確確會輸出true。

這就是 Java 泛型的類型擦除造成的,因為不管是 ArrayList<Integer> 還是 ArrayList<String>,在編譯時都會被編譯器擦除成了 ArrayList。Java 之所以要避免在創建泛型實例時而創建新的類,從而避免運行時的過度消耗。

代碼二

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]
*/

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

運行泛型信息的獲取

但是在有些場景中,我們還是需要獲取泛型信息的。比如,在調用 HTTP 或 RPC 接口時,我們需要進行序列化和反序列的工作。

例如,我們通過一個 HTTP 接口接收如下的 JSON 數據

[{
    "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);
for (Type typeArgument : parameterizedType.getActualTypeArguments()) {
    System.out.println(typeArgument.getTypeName());
}

/* Output
java.lang.String
java.lang.Integer
*/

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

那為什么之前不可以,而這次可以了呢?請注意一個細節

上一節的變量聲明

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

本節的變量聲明

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

其中最關鍵的差別是本節的變量聲明多了一對大括號。有一定 Java 基礎的同學都能看出本節的變量聲明其實是創建了一個匿名內部類。這個類是 HashMap 的子類,泛型參數限定為了 StringInteger

其實在“泛型擦除”一節,我們已經提到,Java 引入泛型擦除的原因是避免因為引入泛型而導致運行時創建不必要的類。那我們其實就可以通過定義類的方式,在類信息中保留泛型信息,從而在運行時獲得這些泛型信息。

簡而言之,Java 的泛型擦除是有范圍的,即類定義中的泛型是不會被擦除的。

框架中的應用

其實很多框架就是使用類定義中的泛型不會被擦除這個特性,實現了相應的功能。

例如,Spring Web 模塊的 RestTemplate,我們可以使用如下寫法:

ResponseEntity<YourType> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference<YourType>() {});

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

總結

Java 泛型擦除是 Java 泛型中的一個重要特性,其目的是避免過多的創建類而造成的運行時的過度消耗。所以,想 ArrayList<Integer>ArrayList<String> 這兩個實例,其類實例是同一個。

但很多情況下我們又需要在運行時獲得泛型信息,那我們可以通過定義類的方式(通常為匿名內部類,因為我們創建這個類只是為了獲得泛型信息)在運行時獲得泛型參數,從而滿足例如序列化、反序列化等工作的需要。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容

  • 泛型是Java 1.5引入的新特性。泛型的本質是參數化類型,這種參數類型可以用在類、變量、接口和方法的創建中,分別...
    何時不晚閱讀 3,046評論 0 2
  • 前面,由于對泛型擦除的思考,引出了對Java-Type體系的學習。本篇,就讓我們繼續對“泛型”進行研究: JDK1...
    賈博巖閱讀 5,175評論 3 28
  • 附上思維導圖。這篇博客主要講了如下知識點。 看完了《Thinking in Java》的第十五章泛型,著實被震了一...
    Happioo閱讀 743評論 0 1
  • 試著發下看有多少人回復你?
    月夜1314閱讀 132評論 0 0
  • 我為什么想要逃離鄉鎮 昨天,同齡的同事哭了,因為貧困戶在檢查組面前回答問題沒有回答好,當即同事被扣上監管不力的帽子...
    啤酒紅茶閱讀 5,617評論 9 6