數組的協變性與範型的不可變性

記得以前面試的時候曾被問過一個問題:數組和List的區別是什麼?當時答的無非就是效率,容量固定,List不能存基本類型等等。當Java發展到了1.5之後,出現了泛型版本的List,又爲這個問題的解答加入了一筆。下面就來講一下與這個話題相關的內容。

1. 數組的協變性。
  數組的協變性(covariant)是指如果類Base是類Sub的基類,那麼Base[]就是Sub[]的基類。而泛型是不可變的(invariant),List<Base>不會是List<Sub>的基類,更不會是它的子類。

數組的協變性可能會導致一些錯誤,比如下面的代碼:

public static void main(String[] args) {   
   Object[] array = new String[10];   
  array[0] = 10;   
}                        

 它是可以編譯通過的,因爲數組是協變的,Object[]類型的引用可以指向一個String[]類型的對象。但是運行的時候是會報出如下異常的:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer

原因就像呼吸一樣簡單。

但是對於泛型就不會出現這種情況了:

public static void main(String[] args) {   
    List< Object> list = new ArrayList< String>();   
    list.add(10);   
}  

這段代碼連編譯都不能通過。

2. 數組的具體化。
 第二個要講的問題是數組是具體化的(reified),而泛型在運行時是被擦除的(erasure)。這句話的意思是數組是在運行時纔去判斷數組元素的類型約束,而泛型正好相反,在運行時,泛型的類型信息是會被擦除的,只有編譯的時候纔會對類型進行強化。

所以上面的例子中,數組的方法會在運行時報出ArrayStoreException,而泛型根本無法通過編譯。

3. 泛型與數組的結合。
這個問題的標題有些誤導,其實泛型與數組根本沒有任何結合的可能性。List<E>[]、List<Integer>[]、或者E[]的寫法都是錯誤的。因爲這些方法無法提供類型的安全性。我們來分別舉2個例子來說明它們會導致的錯誤。

public void testMethod1() {   
    List< Integer>[] array = new List< Integer>[10];   
    List< String> list = new ArrayList< String>();   
    Object[] objs = array;   
    objs[0] = list;   
}  

這段代碼假設array這個泛型數組是可以被創建的,那麼它就可以被Object[]類型的變量引用,進而導致了array中的元素變成了List<String>類型了,發生了類型錯誤。

第二個例子是關於類型參數數組的:

public < E> void testMethod2(E e) {   
    E[] array = new E[10];   
    Object[] objs = array;   
    objs[0] = e;   
    objs[1] = "string";   
    objs[2] = 10;   
    objs[2] = true;   
}  

與上邊的那個例子相似,array可以放入任何類型的元素了,這個是很不安全的。

這裏有一個例外的情況可以說一下,無限制通配符類型的泛型是可以創建泛型數組的。

List< ?>[] listArray = new List< ?>[10];


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章