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。

 

  本文結束!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM