簡體   English   中英

Java集合:列表 <Animal> 老虎=新的ArrayList <Tiger> ()錯誤

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM