引子
《Effective Java》中第25條中《列表優(yōu)于數(shù)組》中提到數(shù)組是協(xié)變的,相反泛型是不可變的
其實(shí)用于描述Java類(lèi)型轉(zhuǎn)換后的繼承關(guān)系一共有三種,協(xié)變,逆變,不可變
其定義為:
如果A、B表示類(lèi)型 f(?) 表示類(lèi)型轉(zhuǎn)換,≤ 表示繼承關(guān)系(比如,A≤B 表示A是由B派生出來(lái)的子類(lèi));
f(?) 是逆變(contravariant)的,當(dāng)
A≤B時(shí)有f(B)≤f(A)成立f(?) 是協(xié)變(covariant)的,當(dāng)
A≤B時(shí)有f(A)≤f(B)成立f(?) 是不變(invariant)的,當(dāng)
A≤B時(shí)上述兩個(gè)式子均不成立,即f(A)與f(B)相互之間沒(méi)有繼承關(guān)系
協(xié)變
數(shù)組是協(xié)變的,那就意味著String是Object的子類(lèi),則String[] 是 Object[]的子類(lèi),但是會(huì)有一個(gè)問(wèn)題:
Object[] objArray = new Integer[1];
objArray[0] = "a string";
這段代碼是合法的,但是在運(yùn)行時(shí)就會(huì)因?yàn)轭?lèi)型不符報(bào)錯(cuò)
不可變
泛型是不可變的,這意味著
ArrayList<Object> objArray = new ArrayList<Object>();
objArray.add("a string");
是無(wú)法通過(guò)編譯的。根據(jù)不可變的定義,ArrayList<Object> 和 ArrayList<String>沒(méi)有繼承關(guān)系
這樣的設(shè)計(jì)是為了保證類(lèi)型安全,根據(jù)《Effective Java》中的說(shuō)法:
// Why generic array creation is illegal - won't compile
List<String>[] stringLists = new ArrayList<String>[1]; // (1)
List<Integer> intList = Arrays.asList(42); // (2)
Object[] objects = stringLists; // (3)
objects[0] = intList; // (4)
String s = stringLists[0].get(0); // (5)
假設(shè)(1)是編譯正確的,那么在(5)的時(shí)候就必然會(huì)出現(xiàn)類(lèi)型不匹配,因?yàn)樗鼑L試把整型賦值給字符類(lèi)型的
泛型類(lèi)型中利用通配符(extends/super)來(lái)實(shí)現(xiàn)協(xié)變和逆變
List<? extends Fruit> 表明每個(gè)item是Fruit/Fruit的子類(lèi),這其實(shí)表明了泛型的上線(xiàn),實(shí)現(xiàn)了協(xié)變
同樣,List<? super Fruit> 表明每個(gè)item都是Fruit/Furit的基類(lèi),這表明了泛型的下線(xiàn),實(shí)現(xiàn)了逆變
泛型的協(xié)變/逆變使用依靠著一個(gè)PECS原則,即Provider Extends Consumer Super
還以L(fǎng)ist為例:
// 前提為Apple為Fruit的派生子類(lèi)
List<? extends Fruit> list = new ArrayList<Apple>();
list.add(new Apple());
這樣的寫(xiě)法是無(wú)法通過(guò)編譯的,會(huì)提示類(lèi)型不符,因?yàn)?code>? extends Furit表明了Furit或者Furit的派生子類(lèi)。如果我們存入Apple,在get時(shí)強(qiáng)轉(zhuǎn)為Apple自然是不會(huì)有問(wèn)題,但是如果我存入Banana,同樣是水果,那么在get時(shí)就會(huì)報(bào)錯(cuò),這樣是類(lèi)型不安全的
泛型通過(guò)擦除來(lái)實(shí)現(xiàn)的,
? extends Furit在編譯階段只是一個(gè)標(biāo)記,和數(shù)組具體化類(lèi)型是不一樣的
那么作為一個(gè)Consumer,應(yīng)當(dāng)使用super
// 前提為Apple為Fruit的派生子類(lèi)
List<? super Fruit> list = new ArrayList<Food>();
list.add(new Apple());
這段代碼是編譯通過(guò)的,只要類(lèi)型要求是Fruit/Fruit的基類(lèi),那么存入的類(lèi)型必定可以強(qiáng)轉(zhuǎn)為Fruit/Fruit的基類(lèi),是類(lèi)型安全的