2013-07-03 106 views
12

我以爲java擦除擦除編譯時的泛型類型,但是當我自己測試它時,我意識到有一些關於字節碼中的泛型類型的信息。Java類型擦除擦除我的泛型?

這裏是我的測試:

我寫了2類:

import java.util.*; 
public class Test { 
    List integerList; 
} 

import java.util.*; 
public class Test { 
    List<Integer> integerList; 
} 

我編譯的類和介於通用類我看到這條線

integerList{blah blah}Ljava/util/List;{blah blah} 
Signature{blah blah}%Ljava/util/List<Ljava/lang/Integer;>;{blah blah}<init> 

in no n通用類:

integerList{blah blah}Ljava/util/List;{blah blah}<init> 

所以很明顯,我有字節碼內的通用信息,所以這個擦除的東西是什麼?

回答

5

某些通用類型信息存儲在Signature屬性中。請參閱JLS 4.84.6JVM spec 4.3.4。閱讀here

也許關於Java泛型最常見的抱怨是,他們沒有具體化 - 有不知道在運行時的方式,一個是List<String>List<Long>任何不同。我已經習慣了這一點,我很驚訝地發現Neil Gafter在超類型令牌上的工作。 事實證明,雖然JVM不會跟蹤泛型類實例的實際類型參數,但它會跟蹤泛型類子類的實際類型參數。換句話說,儘管在運行時新的ArrayList<String>()實際上只是一個新的ArrayList(),但如果一個類擴展了ArrayList<String>,那麼JVM知道StringList的類型參數的實際類型參數。

and Neal Gafter's blog

+2

這是誤導。元數據仍然僅用於反思。它沒有通過。 – Antimony

0

擦除意味着通用類型沒有包含在字節碼中(當創建或使用列表時)。

您看到的簽名僅用於指示該字段是通用的。

1

類型信息將從這裏

integerList = new ArrayList<Integer>(); 

在字節擦除這將是相當於

integerList = new ArrayList(); 

並沒有機會從integerList運行時知道對象是什麼是它的編譯時間類型。

+0

當然!但從「integerList」的定義是列表 JVM可以找出類型 –

+1

是的,與從字段的反映,但如果你得到一個ArrayList的實例將無法​​找到它的通用類型 –

6

什麼是這個擦除的東西?

擦除是從泛型到原始類型的映射。 「由於擦除」這個常見短語基本上沒有意義。什麼是重要的是使用映射的規格。

有兩個有趣的用途。

  • 它用來映射方法簽名從使用泛型到原始類型。這是用於重載的原始類型簽名。這導致絕大多數「擦除」問題。例如,您不能在同一類型中使用兩種方法add(List<String>)add(List<Integer>)。重載可能不是一個好主意,並且不太願意添加此功能。

  • 在運行時可用於對象實例的類型是它在創建時使用擦除的類型。因此,如果您投射到將在運行時檢查的(String),但如果投射到List<String>,則只會檢查該類型的擦除(List)。您可以將類型爲List<String>List<Integer>的變量指向完全相同的實例。在實踐中,你不應該在1.5和更高版本中使用(參考類型的)強制類型。

在可行的情況下,通用信息保存在類文件中並通過反射使其可用。因此,您可以在類定義,超類型,字段,方法,構造函數等上找到它。

+0

謝謝,你的答案幫助我這麼多。 –

3

這是精確使用術語實際上很重要的一個實例:字節碼是Java虛擬機的指令集。一個類文件包含字節碼,也用於連接(現場簽名,方法簽名,...),爲字節碼校驗器,調試器,信息...

類型擦除意味着泛型類型的信息是沒有翻譯成字節碼;更具體地說,泛型類型的所有實例在字節代碼中共享相同的表示。同樣,運行時跟蹤的對象的動態類型(由cast和instanceof操作符使用,並且可以通過getClass()獲得)對於泛型類的所有實例都是相同的,而不考慮源代碼中提供的任何類型參數碼。

您的實驗證明泛型類型信息保留在類文件中,更具體地說,保留在方法和字段簽名的類型中。這並不令人驚訝,因爲簽名實際上是在編譯時使用的。這個might也可以在鏈接時使用,甚至可以通過反射API訪問。關鍵的區別在於它們是宣佈的類型的字段或方法,而不是運行時類型的實際對象。

也就是說,自Java 1.5以來,我們必須區分變量的聲明類型和它引用的對象的運行時類型。前者支持泛型,後者則不支持泛型。是的,這意味着編譯時和運行時類型之間不存在一一對應的關係。