泛型擦除
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的子類了。
從逆變和協變的描述中我們可以總結一下:協變用於下轉上,轉換之后不能再添加任何類型;逆變用於上轉下。
具體什么使用使用協變,什么時候使用逆變,可以參考這篇文章:
其中提到PECS(producer-extends, consumer-super)。比如list.get(0)這種,list作為數據源producer;而list.add(new Apple()),list作為數據處理端consumer。
本文結束!