2015-07-10 76 views
1

我知道泛型類型信息在Java編譯時被刪除,因此無法創建泛型類型的數組(因爲在運行時無法有效地強制插入到數組中)。

但爲什麼不能例外?爲什麼不留下數組的通用類型信息(並且僅限於它們)?

這背後的設計決定是什麼?在我看來,它會使生活更輕鬆,這將有可能做到這一點。爲什麼在Java中的數組中刪除泛型?

T[] genericArray = new T[10]; 
+0

可能向後兼容? – Codebender

+0

這可能是重複的。 – Raedwald

+0

@BendeguzNagy我不知道在T [] genericArray = new T [10];'vs使用Object [] genericArray = new Object [10]'時,你會得到什麼好處? type參數幾乎沒有用處(除非你在向'T [] genericArray'添加一個元素時期待,編譯器應該推導出這個類型並且不允許添加那些不是插入第一個元素類型的元素?當然,這是不可能的,而且要求太多) – CKing

回答

4

簡短的回答:

這是因爲仿製藥的元數據來幫助編譯器幫助您捕捉類型 錯誤,一切都被編譯使用最小公分母 (通常爲Object)和類型轉換。這不是用數組完成的,因爲數組 是它們自己的類。即一個ArrayList<String>ArrayList<Number> 都具有類ArrayList,但String一個陣列具有String[]類和 Number陣列具有Number[]類。

龍答:正在使用泛型將使用至少 公分母(這通常是Object

在編譯時,應有盡有。這表現在 下面的代碼:

public class Generics { 

    public static <T> void print(T what) { 
     System.out.println(what); 
    } 

    public static <T extends Number> void printNumber(T what) { 
     System.out.println(what); 
    } 

    public static void main(String[] args) { 
     Arrays.stream(Generics.class.getDeclaredMethods()) 
       .filter(m -> m.getName().startsWith("print")) 
       .forEach(Generics::print); 
    } 

} 

此打印:

public static void Generics.print(java.lang.Object) 
public static void Generics.printNumber(java.lang.Number) 

所以我們可以看到,當它的編譯它被編譯爲分別ObjectNumber工作方法。

這是這樣的原因,將編譯並運行:

ArrayList<String> list = new ArrayList<>(); 
list.add("foo"); 
ArrayList<Object> list2 = (ArrayList<Object>)(Object)list; 
list2.add(Integer.valueOf(10)); 
System.out.println(list2.get(0)); 
System.out.println(list2.get(1)); 

如果你嘗試,你會看到它打印

foo 
10 

所以由下/上投我們把我們的ArrayList<String>變成了ArrayList<Object>--如果ArrayList實際上將它的內容存儲在類型爲String[]而不是Object[]的數組中,則這是不可能的。

注意,試圖做

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

將導致ClassCastException。這暗示了 編譯器的功能。

請看下面的代碼:

public static void doThingsWithList() { 
    ArrayList<String> list = new ArrayList<>(); 
    list.add(""); 
    String s = list.get(0); 
    print(s); 
} 

在編譯時,它變成了這個字節碼:

public static void doThingsWithList(); 
    Code: 
    0: new   #11     // class java/util/ArrayList 
    3: dup 
    4: invokespecial #12     // Method java/util/ArrayList."<init>":()V 
    7: astore_0 
    8: aload_0 
    9: ldc   #13     // String 
    11: invokevirtual #14     // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z 
    14: pop 
    15: aload_0 
    16: iconst_0 
    17: invokevirtual #15     // Method java/util/ArrayList.get:(I)Ljava/lang/Object; 
    20: checkcast  #16     // class java/lang/String 
    23: astore_1 
    24: aload_1 
    25: invokestatic #17     // Method print:(Ljava/lang/Object;)V 
    28: return 

正如你可以從ArrayList.get結果實際上被轉換爲String20看到。

所以泛型只是語法糖轉化爲自動類型轉換,並帶來額外的好處,即編譯器可以使用此語法糖檢測在運行時會導致ClassCastException的代碼。

現在,爲什麼編譯器不能對String[]Object[]執行相同的操作?不能只是把

public <T> T[] addToNewArrayAndPrint(T item) { 
    T[] array = new T[10]; 
    array[0] = item; 
    System.out.println(array[0]); 
    return array; 
} 

public <T> T[] addToNewArrayAndPrint(T item) { 
    Object[] array = new Object[1]; 
    array[0] = item; 
    System.out.println((T) array[0]); 
    return array; 
} 

沒有。因爲這將意味着,

Arrays.equals(addToNewArray("foo"), new String[]{ "foo" }); 

是假的,因爲第一個數組將有Object[]類和第二將有String[]類。

當然,可以改變Java,以便所有數組的類型爲Object[],並且所有訪問都將使用強制轉換,就像使用泛型一樣。但是,這將打破向後兼容性,而使用泛型不會因爲ArrayList<String>ArrayList具有相同的類。

+0

簡短的回答非常好。 +1 – CKing

1

與結構像new T[10];的問題是,T可以是任何東西,包括Void(這實際上是一個不可級,並會產生一個編譯錯誤)。

如果你仔細想一想,類型是一種語言結構,因此應該在編譯時使用它,而不是在運行時。在某些情況下,運行時類型信息和一些語言實際實現它是有意義的,但是您是否需要它並且是「Good Thing™」是值得商榷的。

有關類型擦除一些有用的信息:https://stackoverflow.com/a/21843984/1417546

+2

即使在運行時,「Void」數組也沒有問題。它只是另一種類型:'Void [] a = new Void [1];'該類型具有單個成員'null'。 – Lii

+0

當我說不可信時,我提到了你不能實例化一個'Void'對象('new Void()'而不是'new Void []'),'Void'的數組實際上是可以接受的。 – EmirCalabuch

+0

但是我在我的答案中澄清了這個概念,並且我錯誤地指出它會生成一個運行時錯誤,並且實際上會產生編譯時錯誤(「構造函數不可見」)。 – EmirCalabuch