泛型擦除java
Java的泛型本質上不是真正的泛型,而是利用了類型擦除(type erasure),好比下面的代碼就會出現錯誤:ui
報的錯誤是:both methods have same erasurespa
緣由是java在編譯的時候會把泛型,上面的<String>和<Integer>都給擦除掉(其實並無真正的被擦除,javap -l -p -v -c能夠看到LocalVariableTypeTable對象
裏面有方法參數類型的簽名)。blog
協變與逆變繼承
理解了類型擦除有助於咱們理解泛型的協變與逆變,現有幾個類以下:get
Plant Fruit Apple Banana Orangeit
其中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。
本文結束!