當前位置:
首頁 > 知識 > Java 的泛型擦除和運行時泛型信息獲取

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

前言

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

Java 的泛型擦除

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

代碼一

Class c1 = new ArrayList.getClass;
Class c2 = new ArrayList.getClass;
System.out.println(c1 == c2);

/* Output
true
*/

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

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

代碼二

List list = new ArrayList;
Map map = new HashMap;
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

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

再看一段代碼

Map map = new HashMap {};

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 map = new HashMap;

本節的變數聲明

Map map = new HashMap {};

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

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

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

框架中的應用

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

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

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

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

總結

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

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

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

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 達人科技 的精彩文章:

JVM類載入過程 & 雙親委派模型
關於微信和支付寶自動退款介面的接入總結
Oracle CDC簡介及非同步在線日誌CDC部署示例
Amazing ASP.NET Core 2.0

TAG:達人科技 |

您可能感興趣

Python 或允許安全工具查看運行時的操作
Windows手機運行WinRT項目獲新進展:運行順暢
如何安全的使用nginx運行IPFS網關
蘋果XP等機型獲認證:都運行iOS12
使用Docker運行常見的Web服務與管理
Windows 微軟常用運行庫合集
AirPower 內運行的是精簡版 iOS,這波操作可還行?
金融時報:運行在閉環系統中的Libra改變不了世界
白鷺引擎發布 版優化打包 iOS/Android App 的運行性能
針對內核中運行的惡意軟體Uroburos的分析
switch漏洞被公開,可以載入linux運行模擬器
Android簡單的布局和運行程序
如何使用 virt-manager 運行虛擬機
三款可以在安卓手機上運行Python代碼的軟體
使用虛擬機在Windows上運行Linux
華為發布 Cyberverse 地圖技術,但最佳的運行設備不是手機
微軟的Azure Stack HCI:運行連接Azure的虛擬化應用程序的新選擇
命令行運行Python腳本時傳入參數的三種方式
谷歌遠程更改了一系列運行Android 9 Pie的手機的設置
簡單對比運行C與python3腳本的執行效率