2015-09-15 98 views
2

我很新來Java的泛型編程。我不明白爲什麼不能創建泛型類型的數組。
爲什麼拋出Object []數組錯誤?

T[] a = new T[size]; //why does this give an error? 

如果泛型類型意味着通用佔位符T將由一個類名在運行時進行更換,是什麼阻止了我們創建具有通用引用數組?

有點搜索之後,我發現了一個解決辦法

T[] a = (T[])new Object[size]; //I don't get how this works? 

雖然我找到了一個解決方案,我仍然無法理解從創建一個通用的陣列是什麼妨礙。 假設我創建了一個返回Object數組的函數。

public Object[] foo(){ 
     return new Object[12]; 
} 

然後撥打電話

String[] a = (String[])foo(); 

給人以ClassCastException。但爲什麼? 它看起來不像第一行代碼,我將Object數組轉換爲T數組嗎?

T[] a = (T[])new Object[size]; 

如果這沒有一個小故障,爲什麼不呢?

+7

你一共有六個問題問一個:) –

+0

@SURESH ATTA我覺得他們所有相關:/ –

+0

@Djack:泛型類型**並不意味着**「泛型佔位符T將在運行時被類名替換」。它在**運行時被'Object'替換**。 Java泛型與C++模板的工作方式不同。 –

回答

3

其中一點是要反過來看。你不能做(String[]) new Object[10];因爲Object陣列是而不是 a String陣列。由於

String[] array = new String[10]; 
array[0] = "foo"; 
String foo = array[0]; 

是好的,但

Object[] objectArray = new Object[10]; 
objectArray[0] = 10; 
String[] stringArray = (String[]) objectArray; 
String foo = stringArray[0]; 

...試圖一個Integer分配給String,這不應該擺在首位被允許。因此,當您將Object[]轉換爲String[]時,此代碼會失敗。該代碼在某處拋出ClassCastException

即使在仿製藥發明之前,這對於Java來說都是一樣的。首先接受所有然後轉向仿製藥。現在

,順便Java泛型是實現意味着,當你編譯代碼,T被悄悄改寫爲Object。所以T[] array = (T[]) new Object[10]默默允許,因爲它實際上被重寫爲Object[] array = new Object[10]。但是一旦你把它拿出來,事情就會出錯。例如,

private static <T> T[] newArray() { 
    return (T[]) new Object[10]; 
} 

如果你打電話String[] array = newArray(),你會在調用點得到了ClassCastException,內newArray()沒有。這就是爲什麼Java會在(T[]) new Object[10]處給你一個警告,而且這個警告很可能會在以後導致真正的ClassCastException

一般來說,不要混合使用數組和泛型。解決所有這些問題的方法是正確使用List

+0

你的回答很有幫助,但是如果'T'被重寫爲'Object'那麼當我創建一個實例會發生什麼?像'你好 = new Hello <>()'T []是否只保留Object []'而不是'String []'? –

+0

是的。是的,'T []'只保留'Object []',然後當你試圖以'String []'的形式取出它時,你會得到'ClassCastException'。 Java會在你抽出泛型並將它們分配給實際類型的地方插入類型轉換。 –

+0

因此,如果在編譯時將泛型佔位符重寫爲'Object',那麼說'T [] = new T [size]'應該不會成爲問題,因爲它將被轉換爲Object [] = new Object [size]' 。但是,編譯器給出了一個錯誤? –

3

處理數組時需要注意幾點。

首先,陣列被認爲是covariant;也就是說,一個類型化數組將保持它的繼承鏈。因此,Integer[]Object[],其方式與IntegerObject相同。

這就是您最後一個例子失敗的原因。你想通過foo投的Object[]String[]

String[] a = (String[])foo(); 

Object[]永遠不會成爲一個String[]因爲一個Object不是String(但相反的將永遠是正確的)。

其次,陣列和泛型不能很好地混合。泛型被認爲是不變式;也就是說,他們不會維持他們的繼承鏈。 A List<Integer>而不是認爲與List<Object>相同。

至於爲什麼您的特定示例失敗,這是由於在編譯時類型擦除。數組在編譯時需要知道它們的具體類型,如果沒有這些信息,就不能實例化。由於泛型不存儲這些信息,因此無法以實例化非泛型數組的方式實例化泛型數組。

也就是說,你必須使用投形式:

T[] a = (T[]) new Object[size]; 

You can read a bit more about generic arrays in this answer,因爲它涵蓋了大多數,你將需要與他們打交道時瞭解的要點。

+0

你說:「...需要在編譯時瞭解它們的具體類型,沒有這些信息,它們不能實例化」但實例化不會在編譯時發生,但運行時和運行時我們知道具體類型類型參數,然後在運行時它可以實例化具體類型的數組。如果我錯了,請糾正我。 – vvtx

+0

@vvtx:是的,你錯了。您無法指定通用參數。數組需要可重用性。 – Makoto

+0

是的,我知道數組需要可重用性,但是在實例化時需要這樣做,即在運行時,並且在運行時您知道具體類型。你說:「你不能指定一個泛型參數」,但是當參數類型是非數組時,它被賦值爲byecode中的Object類型,即在編譯之後T e被替換爲Object e,那麼什麼是停止T [] e到Object [] e? – vvtx

0

數組在運行時知道它們的類型。 A String[]知道它是一組String s。

與此相反,泛型類型參數在運行時被擦除,因此運行時的List<String>只是List

由於類型參數T在運行時不可用new T[10](不能編譯)無法創建真實的T[]

這是不正確的

T[] a = (T[])new Object[size]; 

不能拋出異常。它可以。 Louis Wasserman的例子表明它可以在呼叫站點引起異常,但是該行也可以直接引發異常。例如

public static void main(String[] args) { 
    foo(); 
} 

static <T extends Number> void foo() { 
    T[] array = (T[]) new Object[42]; 
} 

在此,下限的TNumber,所以在運行時,嘗試投一個Object[]Number[],這將引發ClassCastException

您可以創建一個T[]如果你有一個Class<T>對象clazz,例如使用

Array.newInstance(clazz, length); 
相關問題