泛型中的extend以及super

泛型是在Java 1.5中被加入了,這裏不討論泛型的細節問題,這個在Thinking in Java第四版中講的非常清楚,這裏要講的是super和extends關鍵字,以及在使用這兩個關鍵字的時候爲什麼會不同的限制。 
   首先,我們定義兩個類,A和B,並且假設B繼承自A。下面的代碼中,定義了幾個靜態泛型方法,這幾個例子隨便寫的,並不是特別完善,我們主要考量編譯失敗的問題: 
public class Generic{
//方法一
public static <T <span style="background-color: rgb(160, 255, 255);">extends</span> A> void get(List<T <span style="background-color: rgb(160, 255, 255);">extends</span> A> list)
{
    list.get(0);
}

//方法二
public static <T <span style="background-color: rgb(160, 255, 255);">extends</span> A> void set(List<T <span style="background-color: rgb(160, 255, 255);">extends</span> A> list, A a)
{
    list.add(a);
}

//方法三
public static <T super B> void get(List<T super B> list)
{
    list.get(0);
}

//方法四
public static <T super B> void set(List<T super B> list, B b)
{
    list.add(b);
}
}

  編譯之後,我們會發現,方法二和方法三沒有辦法通過編譯。按照Thinking in Java上的說法,super表示下界,而extends表示上界,方法二之所以沒有辦法通過,是因爲被放到List裏面去的可能是A,也可能是任何A的子類,所以編譯器沒有辦法確保類型安全。而方法三之所以編譯失敗,則是因爲編譯器不知道get出來的是B還是B的其他的什麼子類,因爲set方法四允許在list放入B,也允許在list中放入B的子類,也就沒有辦法保證類型安全。 
  上面的這段解釋聽起來可能有點奇怪,都是因爲編譯器無法判斷要獲取或者設置的是A和B本身還是A和B的其他的子類才導致的失敗。那麼Java爲什麼不乾脆用一個關鍵字來搞定呢? 
  如果從下面的角度來解釋,就能把這個爲什麼編譯會出錯的問題解釋的更加的直白和清除,也讓人更容易理解,先看下面的代碼,還是A和B兩個類,B繼承自A: 
public class Generic2{
   public static void main(String[] args){
      List<? <span style="background-color: rgb(160, 255, 255);">extends</span> A> list1 = new ArrayList<A>();
      List<? <span style="background-color: rgb(160, 255, 255);">extends</span> A> list2 = new ArrayList<B>();
      List<? super B> list3 = new ArrayList<B>();
      List<? super B> list4 = new ArrayList<A>();
   }
}

   從上面這段創建List的代碼我們就更加容易理解super和extends關鍵字的含義了。首先要說明的一點是,Java強制在創建對象的時候必須給類型參數制定具體的類型,不能使用通配符,也就是說new ArrayList<? extendsA>(),new ArrayList<?>()這種形式的初始化語句是不允許的。 
   從上面main函數的第一行和第二行,我們可以理解extends的含義,在創建ArrayList的時候,我們可以指定A或者B作爲具體的類型,也就是,如果<? extends X>,那麼在創建實例的時候,我們就可以用X或者擴展自X的類爲泛型參數來作爲具體的類型,也可以理解爲給?號指定具體類型,這就是extends的含義。 
   同樣的,第三行和第四行就說明,如果<? super X>,那麼在創建實例的時候,我們可以指定X或者X的任何的超類來作爲泛型參數的具體類型。 
   當我們使用List<? extends X>這種形式的時候,調用List的add方法會導致編譯失敗,因爲我們在創建具體實例的時候,可能是使用了X也可能使用了X的子類,而這個信息編譯器是沒有辦法知道的,同時,對於ArrayList<T>來說,只能放一種類型的對象。這就是問題的本質。而對於get方法來說,由於我們是通過X或者X的子類來創建實例的,而用超類來引用子類在Java中試合法的,所以,通過get方法能夠拿到一個X類型的引用,當然這個引用可以指向X也可以指向X的任何子類。 
   而當我們使用List<? super X>這種形式的時候,調用List的get方法會失敗。因爲我們在創建實例的時候,可能用了X也可能是X的某一個超類,那麼當調用get的時候,編譯器是無法準確知曉的。而調用add方法正好相反,由於我們使用X或者X的超類來創建的實例,那麼向這個List中加入X或者X的子類肯定是沒有問題的,因爲超類的引用是可以指向子類的。 
  最後還有一點,這兩個關鍵字的出現都是因爲Java中的泛型沒有協變特性的倒置的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章