軟件構造——Java中的型變與泛型

型變:當子類型關係出現在更加複雜類型中時,新類型中有着怎樣的類型關係?
如:
Cat是Animal的子類,那麼List 和List有什麼關係?

型變有如下三種情況:
Covariant(協變): 保持子類型關係List是List的子類
Contravariant(逆變):反轉了子類型關係 ,如List是List的子類
Invariant(不變): 沒有子類型關係,如List nor 和List均不具備父類型與子類型關係
圖示是子類型中重寫父類型方法,參數和返回值的協變逆變情況:
在這裏插入圖片描述

Java支持協變,不支持逆變。當發生類似於逆變的情況時,編譯器默認子類型與父類型中的方法是重載關係,而不是重寫。如下面的例子:
在這裏插入圖片描述可以得到如下輸出:
在這裏插入圖片描述
上圖說明Java編譯器不支持逆變,在編譯階段沒有將子類型中callAnimal()方法的Object參數類型逆變成父類型的Animal,而是把它們當成參數類型不同處理。
這裏,子類型中的callAnimal()方法與父類型中的callAnimal()方法在運行階段發生了重載。故而根據參數聲明的類型不同,動態選擇子類型與父類型中名字均爲callAnimal()的方法。

Java泛型不支持型變:
在這裏插入圖片描述
在這裏插入圖片描述
虛擬機中沒有泛型類型對象。泛型信息只存在於編譯階段,在運行時會被”擦除”,並替換爲限定類型,如果沒有限定類型則爲Object類型。

泛型中不能調用具體類型的相關方法,但是list中的contains()會自動調用類型的equals()方法
在限定了T的具體類型後,可以調用類型相關的具體方法。

運行時類型查詢只適用於原始類型,
在這裏插入圖片描述輸出
在這裏插入圖片描述

因爲運行時,所有的數據類型均要被擦除,還原爲raw type,這樣,List中的對象的數據類型也將不復存在。因此,if(cats instanceof List<?>)反倒編譯通過。
在這裏插入圖片描述

具有限定類型下的list操作
List是 List<? extends Animal>的子集
1.對於類型 List<? extends Animal>,調用一個返回Animal類型(或子類型)的方法(如:T get(int pos))是安全的, 因爲compiler知道這個List中的任何對象至少具有Animal類型(或子類型),可以完成類型轉換。
2.但調用類似add(E e)的方法則不安全,類型擦除機制會導致運行時可以往animals中儲存各種類型的對象。因此,Java此時禁止了List中所有具有泛型輸入參數的方法,如:add(T item)
在這裏插入圖片描述註釋掉中間的三行add()操作後,
在這裏插入圖片描述
List是 List<? super Cat>的子集
1.對於List中的 T get(int pos)方法,當指定類型是“? super Cat”時, get方法的返回類型就變成了“? super Cat”, 即返回類型可能是Cat或者Cat的基類型,compiler無法確定具體類型,因此拒絕調用任何返回類型爲T的方法(除非是讀取爲Object類)
2.但調用類似add(E e)的方法則安全, 傳入Cat及其子類(WhiteCat)是安全的, 因爲compiler知道這個List包含的是Cat或Cat的基類對象。因此,Java此時禁止了List中所有具有泛型返回類型的方法,如:get()
在這裏插入圖片描述
將編譯錯誤行註釋掉,
在這裏插入圖片描述
可得到輸出:
在這裏插入圖片描述

上述規則可簡記爲“get-extends, add-super”

通配符下的子類型關係
在這裏插入圖片描述

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