假設我們有以下的類別層次:

Layer1 --> Layer2 --> Layer3 --> Layer4

其中Layer1是位于最高位置的基類,Layer2Layer1的直接子類,Layer3又是Layer2的直接子類,等等.

我們在使用數組時會有這樣的用法:

Layer1[] layerArray=new Layer2[10];

此時盡管運行時可能產生某些錯誤,例如往一個明明是Layer2的數組中加入一個Layer1的實例,但編譯期并不會有什么問題.這是因為Java的數組存在稱之為協變的現象,即如果Layer2Layer1的子類,那么Layer2的數組也是Layer1的數組的子類.我們可以這樣來驗證:

Layer2[] layer2Array=new Layer2[10];

System.out.println(layer2Array instanceof Layer1[]);

程序輸出的結果為true.

Layer1[] layerArray=new Layer2[10];這樣寫有一個很大的缺點,那就是你可以寫

layerArray[0]=new Layer1();

這樣的代碼,編譯器不會報錯,但實際上我們往一個Layer2的數組里塞了一個Layer1的實例,這在運行時會扔出一個java.lang.ArrayStoreException。即是說數組沒有提供編譯期的類型檢查。

而容器(例如List)很容易被我們認為是另一種數組,只是能夠容納各種類型以及動態改變大小而已。在JavaSE5之前,即使這樣認為也不會給編程帶來麻煩,因為容器沒有辦法指定所容納的具體類型。但在引入泛型之后,我們可能為了編譯期的類型檢查以及動態改變大小兩個理由而迫不及待的用容器徹底代替數組,也就有可能想當然的寫出這樣的代碼:

List<Layer1> list=new ArrayList<Layer2>();

這樣寫的理由很簡單,總是希望用基類型的引用來提供靈活性。然而遺憾的是,上述代碼編譯不能通過(或許編譯期就不能通過這一點,反而是好事),原因就在于容器沒有“協變”現象,一個Layer2List并不是Layer1List的子類,而是完全不同的類。因此上面的代碼同

Integer i=new String("你好");

一樣荒謬,自然逃不過編譯器的法眼。

難道就不能用泛型創建這樣一種List的引用,類型參數只使用基類型,而在實例化的時候可以使用子類型,但又可以借助泛型的編譯期檢查么?

例如我想要一個List<Layer1>類型的引用,這個引用可以指向ArrayList<Layer2>ArrayList<Layer4>(因為我不想關心實例化時的具體類型),但是一旦實例化一個ArrayList<Layer2>以后,又能借助編譯器來檢查向其中添加的確實是Layer2的實例,而非StringInteger。這能不能辦到呢?

答案是不能,雖然初試之下,我們可能會利用泛型通配符想當然的寫出如下代碼:

List<? extends Layer1> layer1List=new ArrayList<Layer2>();

乍看之下,代碼的本意是要建立一個泛型List的引用,而泛型的類別參數是任何Layer1的子類均可,這不正好合我們剛才所說的意思嗎?而且這回編譯器也沒有報錯,應該OK了吧!

接下來的事情卻讓人哭笑不得,這個List竟然不能添加任何元素,無論是寫

layer1List.add(new Layer1());

還是寫

layer1List.add(new Layer2());

均報錯(編譯期即錯,不用待到運行時)。甚至是

layer1List.add(new Object());

也報錯(這證明了無論提供的類型參數是Layer1的子類還是超類,亦或者Layer1本身,均報錯)。

這是為什么呢?(給讀者3分鐘思考,然后帶著詭異的微笑揭曉答案)

原來extends關鍵字圈定的是泛型參數的上界,回頭單看

List<? extends Layer1>

這一句,其實說的是,我有一個泛型的List,它的類別參數是Layer1的一個子類,因此如果這個子類是Layer3,那么Layer2以上的類別就不能向該List中添加(這個沒什么問題吧,別繞進去了哦),如果這個子類別是Layer4,那么Layer3以上的類別就不能向該List中添加(想象我們的類別體系還存在Layer5,Layer6等等,顯然子類別是哪一個都有可能,那就是說拒絕任何一個類別都是說得通的)。因此編譯器根本無法確定這個List到底可以放什么不可以放什么,所以統統拒絕。

更正式一點說,extends給了一個上界,只有該界以下的類別才能合法的添加到List中,但是這個上界本身都是不確定的(它可以無限往類別體系的下方移動),自然也就說不出哪些類別是合法的了。

結論:想要容器提供類似于數組那樣的協變效果,而又要有類型檢查,至少在我所知范圍,辦不到。