Java泛型的協變與逆變

  泛型擦除

  Java的泛型本質上不是真正的泛型,而是利用了類型擦除(type erasure),比如下面的代碼就會出現錯誤:

  

  報的錯誤是:both methods  have same erasure

  原因是java在編譯的時候會把泛型,上面的<String>和<Integer>都給擦除掉(其實並沒有真正的被擦除,javap -l -p -v -c可以看到LocalVariableTypeTable

裏面有方法參數類型的簽名)。

 

  協變與逆變

 

  理解了類型擦除有助於我們理解泛型的協變與逆變,現有幾個類如下:

  Plant  Fruit  Apple  Banana  Orange

  其中Apple、Banana、Orange是Fruit的子類,Fruit是Plant的子類。我們來看下下面的代碼:

  

  這裏有些同學可能不明白,爲什麼編譯會報錯呢?ArrayList是List的子類,Apple是Fruit的子類,那麼我這裏的泛型轉換爲什麼出問題呢?

  因爲泛型沒有內建的協變類型,無法將List<Fruit>和ArrayList<Apple>關聯起來,所以在編譯階段就會出現錯誤。

 

  協變

  於是我們可以利用通配符實現泛型的協變:<? extends T>子類通配符;這個通配符定義了?繼承自T,可以幫助我們實現向上轉換:

  

  看起來很美好吧,其實不然。這裏我們要理解當轉換之後list中的數據類型是什麼。雖然將Apple類型賦值給了list,但是list的類型是? extends Fruit,

把? extends Fruit看成一個整體,我們能確定list的具體類型肯定是Fruit或者Fruit的父類(因爲一個類只能有一個直接父類,所以確定了Fruit,那麼Fruit的父類

則都是可以確定的),而不能確定list的類型是Fruit的子類當中具體的哪一個?(有多個類都繼承自Fruit),所以這也就直接導致了一旦使用了<? extends T>

向上轉換之後,不能再向list中添加任何類型的對象了,這個時候只能選擇從list當中get數據而不能add。

  

  另外還需要注意的是,這個時候從list當中get出來的數據不再是Apple,而是Fruit或者Fruit的父類:

  

  

  逆變

  逆變則和協變相反,它是向下轉換:

  

  逆變使用通配符? super T(超類通配符),如上面代碼,Fruit是Apple的超類,則這個時候對於JVM來說,它能確定list的類型的超類肯定是Apple

或者Apple的父類,換言之該類型就是Apple或者Apple的子類,所以和上面的協變一樣,既然確定了類型的範圍,那麼list能夠add的類型也就是Apple或者Apple的子類了。

 

  從逆變和協變的描述中我們可以總結一下:協變用於下轉上,轉換之後不能再添加任何類型;逆變用於上轉下。

  具體什麼使用使用協變,什麼時候使用逆變,可以參考這篇文章:

  Java泛型(二) 協變與逆變

  其中提到PECS(producer-extends, consumer-super)。比如list.get(0)這種,list作爲數據源producer;而list.add(new Apple()),list作爲數據處理端consumer。

 

  本文結束!

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