2011-12-13 230 views
20

數組其實,問題應該是創建泛型集合

Creating an array of generic anything. 

爲什麼不能編譯照顧呢?

以下內容將被標記爲錯誤 - 無法創建通用數組。

List<MyDTO>[] dtoLists = {new ArrayList<MyDTO>(), anExistingDtoList}; 

爲了克服這一點,我需要

List<MyDTO>[] dtoLists = (List<MyDTO>[])Array.newInstance(ArrayList.class, 2); 
dtoLists[0] = new ArrayList<MyDTO>(); 
dtoLists[1] = anExistingDtoList; 

那麼,爲什麼不能編譯器第一種情況轉換爲第二種情況?

我知道泛型是編譯時確定的,而不是運行時確定的,而數組是運行時確定的,因此需要確定類型才能創建數組。

編譯器設計人員遇到什麼樣的技術/邏輯障礙會阻止他們實現這個目標?

問題純粹是關於語言正交性的哲學問題嗎?如果是這樣,這種行爲將如何違反語言的正交性?

這是一個複雜性問題嗎?解釋複雜性。

我希望我的問題的答案能讓我更好地瞭解java編譯器在涉及泛型時的行爲。

附註: 來吧,停止被觸發快樂。答案Array of Generic List 不回答我的問題。爲什麼編譯器不能自發地執行轉換?

+0

[Array of Generic List](http://stackoverflow.com/questions/7810074/array-of-generic-list) – Thilo

+1

的可能重複檢查出該重複的最佳答案。它有一個例子,爲什麼這是不被允許的。 – Thilo

+2

這不是重複的。我的問題要求有關編譯器設計問題的答案。 –

回答

8

其實Java那樣創建可變參數的泛型數組,所以你可以做

List<MyDTO>[] dtoLists = array(new ArrayList<MyDTO>(), anExistingDtoList); 

@SafeVarargs 
static <E> E[] array(E... array) 
{ 
    return array; 
} 

至於爲什麼是明確的泛型數組創建禁止的,它是與類型擦除。 (同樣的擔憂存在於上述解決方案中,但被@SafeVarargs壓制)然而,這是值得商榷的;有不同的方法來處理這個問題,編譯器警告可能就足夠了。但他們選擇了徹底禁止它,可能是因爲數組都不再重要反正現在我們已經泛型集合

+0

作爲一名C++程序員,Java中的數組對我非常有用。在Java中,我無法傳遞指針。我必須在對象或數組中包含一個變量,以便像指針一樣傳遞它。 –

+0

我的意思是,在泛型之前,我們不能'列出',因此爲靜態類型設置'Foo []'很重要。這個重要性已經消失。當然,數組仍然是重要的基本構建塊。 – irreputable

3

我知道,相對於此問題的解決方法,Array.newInstance()是一種昂貴的調用方法。 IIRC它使用本地方法來實例化數組,在涉及的其他反射中。我無法提供任何統計信息,但這似乎是足夠好的原因,因此編譯器不會自動替換此功能,以便允許創建通用數組。特別是考慮到ArrayList等的存在,它似乎不是一個緊迫的問題。

1

編譯器可以自發地進行轉換,他們只是規定不因爲通用陣列不能表現像非泛型數組。

參見10.5. Array Store Exception

對於數組類型爲A[],其中A是引用類型,分配到該陣列的一個組件在運行時進行檢查,以確保所分配的值是分配到組件。

如果所分配的值的類型與組件類型不是分配兼容的,則會引發ArrayStoreException

如果數組的組件類型不可確定,則Java虛擬機無法執行上一段中描述的存儲檢查。這就是爲什麼禁止使用不可指定元素類型的數組創建表達式的原因。

如果我們把一些其他種類的List在它List<MyDTO>[]不會丟,所以它不表現爲一個數組。請注意報價中的最後一句:「這就是爲什麼禁止使用不可指定元素類型的數組創建表達式。」這就是原因,它被指定爲這樣。 (而且,備案,this reasoning has always existed,所以它是存在,當這個問題被張貼在2011年)

我們仍然可以做到這一點:

@SuppressWarnings({"unchecked","rawtypes"}) 
List<MyDTO>[] dtoLists = new List[] { 
    new ArrayList<MyDTO>(), anExistingDtoList 
}; 

或者這樣:

@SuppressWarnings("unchecked") 
List<MyDTO>[] dtoLists = (List<MyDTO>[]) new List<?>[] { 
    new ArrayList<MyDTO>(), anExistingDtoList 
}; 

(除了靜態地檢查參數類型之外,可變參數的東西是等價的:它可以是creates a List[]suppresses warnings。)

現在,當然,規範可以改變「如果所分配的值的類型與的原始類型的元件類型...」不是分配兼容的,但是要點是什麼?它會在一些不尋常的情況下保存少數人物,但是否則會禁止那些不瞭解其含義的人發出警告。

此外,我看到the tutorial和其他典型解釋沒有證明的是如何烘焙到類型系統協變陣列。

例如,給出如下聲明:

// (declaring our own because Arrays.fill is defined as 
// void fill(Object[], Object) 
// so the next examples would more obviously pass) 
static <T> void fill(T[] arr, T elem) { 
    Arrays.fill(arr, elem); 
} 

你知道,這個編譯?

// throws ArrayStoreException 
fill(new String[1], new Integer(0)); 

而這個編譯過:

static <T, U extends T> void fill(T[] arr, U elem) {...} 

但是,這只是一個:

// doesn't throw ArrayStoreException 
fill(dtoLists, new ArrayList<Float>()); 

的Java 8之前,我們可以到fill失敗給它下面的聲明使這些電話類型推斷問題,現在它工作「正確」,一味地將List<Float>放入List<MyDTO>[]

這叫做堆污染。它可能會導致ClassCastException在某個時間後拋出,可能與實際導致問題的操作完全無關。像List這樣的通用容器造成的堆污染需要更加明顯的不安全行爲,例如使用raw types,但在這裏,我們可以隱式導致堆污染,並且不會有任何警告。

通用數組(通常是數組)實際上只允許我們在最簡單的情況下進行靜態檢查。

因此很明顯,語言設計者認爲最好不要讓他們,而瞭解他們存在問題的程序員可以抑制警告並繞過限制。