假設(shè)我們有以下的類別層次:
Layer1 --> Layer2 --> Layer3 --> Layer4
其中Layer1是位于最高位置的基類,Layer2是Layer1的直接子類,而Layer3又是Layer2的直接子類,等等.
我們在使用數(shù)組時(shí)會有這樣的用法:
Layer1[] layerArray=new Layer2[10];
此時(shí)盡管運(yùn)行時(shí)可能產(chǎn)生某些錯(cuò)誤,例如往一個(gè)明明是Layer2的數(shù)組中加入一個(gè)Layer1的實(shí)例,但編譯期并不會有什么問題.這是因?yàn)?/span>Java的數(shù)組存在稱之為”協(xié)變”的現(xiàn)象,即如果Layer2是Layer1的子類,那么Layer2的數(shù)組也是Layer1的數(shù)組的子類.我們可以這樣來驗(yàn)證:
Layer2[] layer2Array=new Layer2[10];
System.out.println(layer2Array
instanceof Layer1[]);
程序輸出的結(jié)果為true.
像Layer1[] layerArray=new Layer2[10];這樣寫有一個(gè)很大的缺點(diǎn),那就是你可以寫
layerArray[0]=new Layer1();
這樣的代碼,編譯器不會報(bào)錯(cuò),但實(shí)際上我們往一個(gè)Layer2的數(shù)組里塞了一個(gè)Layer1的實(shí)例,這在運(yùn)行時(shí)會扔出一個(gè)java.lang.ArrayStoreException。即是說數(shù)組沒有提供編譯期的類型檢查。
而容器(例如List)很容易被我們認(rèn)為是另一種數(shù)組,只是能夠容納各種類型以及動態(tài)改變大小而已。在JavaSE5之前,即使這樣認(rèn)為也不會給編程帶來麻煩,因?yàn)槿萜鳑]有辦法指定所容納的具體類型。但在引入泛型之后,我們可能為了編譯期的類型檢查以及動態(tài)改變大小兩個(gè)理由而迫不及待的用容器徹底代替數(shù)組,也就有可能想當(dāng)然的寫出這樣的代碼:
List<Layer1> list=new ArrayList<Layer2>();
這樣寫的理由很簡單,總是希望用基類型的引用來提供靈活性。然而遺憾的是,上述代碼編譯不能通過(或許編譯期就不能通過這一點(diǎn),反而是好事),原因就在于容器沒有“協(xié)變”現(xiàn)象,一個(gè)Layer2的List并不是Layer1的List的子類,而是完全不同的類。因此上面的代碼同
Integer i=new
String("你好");
一樣荒謬,自然逃不過編譯器的法眼。
難道就不能用泛型創(chuàng)建這樣一種List的引用,類型參數(shù)只使用基類型,而在實(shí)例化的時(shí)候可以使用子類型,但又可以借助泛型的編譯期檢查么?
例如我想要一個(gè)List<Layer1>類型的引用,這個(gè)引用可以指向ArrayList<Layer2>或ArrayList<Layer4>(因?yàn)槲也幌腙P(guān)心實(shí)例化時(shí)的具體類型),但是一旦實(shí)例化一個(gè)ArrayList<Layer2>以后,又能借助編譯器來檢查向其中添加的確實(shí)是Layer2的實(shí)例,而非String或Integer。這能不能辦到呢?
答案是不能,雖然初試之下,我們可能會利用泛型通配符想當(dāng)然的寫出如下代碼:
List<? extends
Layer1> layer1List=new
ArrayList<Layer2>();
乍看之下,代碼的本意是要建立一個(gè)泛型List的引用,而泛型的類別參數(shù)是任何Layer1的子類均可,這不正好合我們剛才所說的意思嗎?而且這回編譯器也沒有報(bào)錯(cuò),應(yīng)該OK了吧!
接下來的事情卻讓人哭笑不得,這個(gè)List竟然不能添加任何元素,無論是寫
layer1List.add(new Layer1());
還是寫
layer1List.add(new Layer2());
均報(bào)錯(cuò)(編譯期即錯(cuò),不用待到運(yùn)行時(shí))。甚至是
layer1List.add(new Object());
也報(bào)錯(cuò)(這證明了無論提供的類型參數(shù)是Layer1的子類還是超類,亦或者Layer1本身,均報(bào)錯(cuò))。
這是為什么呢?(給讀者3分鐘思考,然后帶著詭異的微笑揭曉答案)
原來extends關(guān)鍵字圈定的是泛型參數(shù)的上界,回頭單看
List<? extends
Layer1>
這一句,其實(shí)說的是,我有一個(gè)泛型的List,它的類別參數(shù)是Layer1的一個(gè)子類,因此如果這個(gè)子類是Layer3,那么Layer2以上的類別就不能向該List中添加(這個(gè)沒什么問題吧,別繞進(jìn)去了哦),如果這個(gè)子類別是Layer4,那么Layer3以上的類別就不能向該List中添加(想象我們的類別體系還存在Layer5,Layer6等等,顯然子類別是哪一個(gè)都有可能,那就是說拒絕任何一個(gè)類別都是說得通的)。因此編譯器根本無法確定這個(gè)List到底可以放什么不可以放什么,所以統(tǒng)統(tǒng)拒絕。
更正式一點(diǎn)說,extends給了一個(gè)上界,只有該界以下的類別才能合法的添加到List中,但是這個(gè)上界本身都是不確定的(它可以無限往類別體系的下方移動),自然也就說不出哪些類別是合法的了。
結(jié)論:想要容器提供類似于數(shù)組那樣的協(xié)變效果,而又要有類型檢查,至少在我所知范圍,辦不到。