[英]Java Collections: List<Animal> tiger = new ArrayList<Tiger>() WRONG
老虎類是動物類的延伸。
當我聲明時: List<Animal> tiger = new ArrayList<Tiger>();
。 我會在編譯時出錯。
但是,我認為這條線適用於多態性。 請誰能為我解釋。
你做不到
List<Animal> tiger = new ArrayList<Tiger>();
在java中。 左邊的通用類型必須與右邊的通用類型完全相等(或者,如果通配符在游戲中,則可能不必相等- ? extends T
或? super T
)。
如果有可能,則不可能將新的Lion
添加到聲明為Animal
列表的列表中-這沒有任何意義。
您可以做的是:
List<Animal> tigers = new ArrayList<Animal>();
tigers.add(new Tiger());
(所有Animal
家族,包括Tiger
家族)
要么:
List<? extends Animal> tigers = new ArrayList<Tiger>();
tigers.add(new Tiger()); // Adding is immpossible now - list can be read only now!
(僅Animal
子類) -列表現在只能讀取!
List<Animal>
將允許您添加一只可愛的小狗。 ArrayList<Tiger>
中的哪些老虎隨后會吃掉。
多態地說,您將擁有
List<Tiger> tigers = new ArrayList<Tiger>();
如果需要,您可以依賴並使用接口定義的功能來替換List<Tiger>
任何實現。 您要嘗試的不是多動症,這只是不安全的轉換(尤其是對於上述小狗),並且由於上述原因而無法使用。
原因是基於Java如何實現泛型。 我發現最好的解釋方法是首先使用數組。
數組示例
使用數組,您可以執行以下操作:
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
但是,如果嘗試這樣做會發生什么?
Number[0] = 3.14; //attempt of heap pollution
最后一行可以正常編譯,但是如果運行此代碼,則可能會得到ArrayStoreException
。
這意味着您可以欺騙編譯器,但不能欺騙運行時類型系統。 之所以這樣,是因為數組是我們所謂的可更新類型 。 這意味着在運行時Java知道此數組實際上是作為整數數組實例化的,而該數組恰好是通過Number[]
類型的引用進行訪問的。
因此,正如您所看到的,一件事是對象的實際類型,另一件事是用於訪問對象的引用的類型,對嗎?
Java泛型的問題
現在,Java泛型類型的問題在於類型信息被編譯器丟棄,並且在運行時不可用。 此過程稱為類型擦除 。 在Java中實現這樣的泛型是有充分的理由的,但這是一個很長的故事,它與預先存在的代碼的二進制兼容性有關。
但是這里的重點是,由於在運行時沒有類型信息,因此無法確保我們不會造成堆污染。
例如,
List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts;
myNums.add(3.14); //heap polution
如果Java編譯器沒有在編譯時阻止您執行此操作,則運行時類型系統也無法阻止您執行此操作,因為在運行時無法確定此列表僅應為整數列表。 Java運行時允許您將只包含整數的所有內容放入此列表中,因為在創建時將其聲明為整數列表。
因此,Java的設計人員確保您不能欺騙編譯器。 如果您不能欺騙編譯器(就像我們對數組所做的那樣),那么您也不能欺騙運行時類型系統。
因此,我們說泛型是不可更改的 。
顯然,這也會阻礙多形性。 解決方案是學習使用Java泛型的兩個強大功能,即協方差和逆方差。
協方差
使用協方差,您可以從結構中讀取項目,但不能在其中寫入任何內容。 所有這些都是有效的聲明。
List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>()
List<? extends Number> myNums = new ArrayList<Double>()
您可以從myNums
讀取:
Number n = myNums.get(0);
因為您可以確定實際列表中包含的內容,都可以將其向上轉換為Number(所有擴展Number的東西都是Number,對嗎?)
但是,不允許您將任何內容放入協變結構中。
myNumst.add(45L);
這是不允許的,因為Java無法保證實際對象的實際類型是什么。 它可以是擴展Number的任何內容,但是編譯器不能確定。 因此您可以閱讀,但不能書寫。
逆差
有了相反性,您可以做相反的事情。 您可以將事物放入通用結構中,但不能從中讀出。
List<Object> myObjs = new List<Object();
myObjs.add("Luke");
myObjs.add("Obi-wan");
List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);
在這種情況下,對象的實際性質是“對象列表”,通過逆變,您可以將數字放入其中,這主要是因為數字以對象為共同祖先。 因此,所有數字都是對象,因此這是有效的。
但是,假設您將得到一個數字,那么您將無法安全地從此反結構中讀取任何內容。
Number myNum = myNums.get(0); //compiler-error
如您所見,如果編譯器允許您編寫此行,則在運行時將收到ClassCastException。
獲取/放置原理
因此,在僅打算將通用值從結構中取出時,請使用協方差;在僅打算將通用值放入結構中時,請使用逆方差;當您打算同時使用兩者時,請使用確切的通用類型。
我最好的例子是將以下任何一種數字從一個列表復制到另一個列表。
public static void copy(List<? extends Number> source, List<? super Number> destiny) {
for(Number number : source) {
destiny.add(number);
}
}
得益於協方差和逆方差的強大功能,它可以在以下情況下工作:
List<Integer> myInts = asList(1,2,3,4);
List<Integer> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();
copy(myInts, myObjs);
copy(myDoubles, myObjs);
我同意這令人困惑。 如果允許該類型的語句,這可能會出錯:
List<Tiger> tigers = new ArrayList<Tiger>(); // This is allowed.
List<Animal> animals = tigers; // This isn't allowed.
tigers.add(new Lion()); // This puts a Lion in tigers!
哦。 是的,你是真的。 您的代碼在多態思維中是正確的。 但是,看看我只是個菜鳥時使用了很長時間的代碼。 您將了解為什么要感謝Collection
:
class Animal{
}
class Tiger extends Animal{
}
public class Test {
public static void main (String[] args){
List<Animal> animal = new ArrayList<Animal>(); //obvious
List<Tiger> tiger = new ArrayList<Tiger>(); //obvious
List<Animal> tigerList = new ArrayList<Tiger>(); //error at COMPILE-TIME
Animal[] tigerArray = new Tiger[2]; //like above but no error but....
Animal tmpAnimal = new Animal();
/*
* will meet RUN-TIME error at below line when use Array
* but Collections can prevent this before at COMPILE-TIME
*/
tigerArray[0] = tmpAnimal; //Oh NOOOO. RUN-TIME EXCEPTION
/*
* Below examples WRONG for both Collection and Array
* Because here is Polymorphism problem. I just want to make more clearer
*/
List<Tiger> animalList = new ArrayList<Animal>();
Tiger[] animalArray = new Animal[2];
}
}
如您在上面的代碼中所見,當您阻止使用List<Animal> tigerList = new ArrayList<Tiger>();
時, Collections
是如此“智能” List<Animal> tigerList = new ArrayList<Tiger>();
您應該想象是否有人使用: tigerList.add(a Lion, a Cat,......);
--->錯誤。
因此,總結一下,這是不同的:
數組:在運行時檢查。 您會感到更舒適,但危險
集合:在編譯時檢查。 您會生氣,因為它會注意到錯誤。 但是,您將在運行時防止錯誤。 !!!!
也許下面的帖子已經解決了您的問題。 但我建議您使用WildCard,例如:
List<? extends Animal> tigerList = new ArrayList<Tiger>();
是。 您可能會在此行后面看到這個想法。 但是,最有趣的事情是:它將阻止您更改列表。 在這種情況下,請add
方法。 例如:
tigerList.add(TIGER); ERROR
是。 這也將阻止您添加老虎:)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.