1. 程式人生 > >聊一聊Java泛型的擦除

聊一聊Java泛型的擦除

java se 實現 c++ 通過 lis 引用 可能 需要 這也

最近看了《thinking in java》的第十五章泛型,感覺有些東西需要記錄下來。

泛型是Java SE5才被引入的概念,現在我的工作中泛型主要使用在集合,這樣可以知道set()和get()的類型(類型檢查是在編譯階段,可以使用反射繞過編譯),而不必再進行額外的轉型操作。

今天,我們主要是來聊一聊Java泛型的擦除。

或許,你對Java泛型的擦除的概念不是很理解,下面我們來看一個例子:

技術分享圖片

很多人會認為ArrayList<String>和ArrayList<Integer>是不同的類型,但是這段程序打印的結果是true。

下面我們再來看一個例子:

技術分享圖片

這種代碼在Java中是不能編譯通過的,即使我傳的是baby這個類的對象。但是這種代碼在C++中不僅能編譯通過還能執行。

這種在泛型代碼內部獲取不到有關泛型參數類型的一種泛型實現並不是Java的語言特性,而是Java泛型實現的一種折中。

Java的泛型是使用擦除實現的。這也就意味著當你在使用泛型的時候,任何具體的類型信息都被擦除了,你唯一知道的是你在使用一個對象。就如同上例的ArrayList<String>和ArrayList<Integer>在運行是相同的類型,它們都被擦出成它們的原生類型,List。

如果,泛型在Java 1.0中就已經是其中一部分,那麽這個特性很可能就不會有擦除來實現,將會使用具體化,使類型參數保持一致,因此就可以在類型參數上執行基於類型語言的操作和反射操作。

同樣,擦除的代價也是巨大的。泛型不能用於顯式的引用運行時類型的操作之中,例如轉型,instanceof和new。

即使,擦除在方法體內移除了有關實際類型的信息,編譯器仍能夠確保在方法或者類中使用的類型的內部一致性。以為擦除在方法體內移除了類型信息,所以運行時的問題的就是邊界:對象進入和離開的方法的地點。這些就是編譯器在編譯期執行類型檢查並插入轉型代碼的地點。下面的例子很好的說明了這點。

技術分享圖片

下面是用Javap -c Test反編譯這個類看到的內容

技術分享圖片

set()和get()方法將直接存儲和產生值,轉型是在調用get()的時候接受檢查的。

下面我們用上泛型再看看,

技術分享圖片

反編譯的內容如下:

技術分享圖片

所產生的字節碼相同,對進入set()的類型檢查是不需要的,因為這由編輯器執行。而對從get()返回的值仍需進行轉型。

所以在泛型中的所有動作都發生在邊界處。

聊一聊Java泛型的擦除