使用通配符的原因:Java中的數(shù)組是協(xié)變的山害,但是泛型不支持協(xié)變阿蝶。
數(shù)組的協(xié)變
首先了解下什么是數(shù)組的協(xié)變晴圾,看下面的例子:
Number[] nums = new Integer[10]; // OK
因為Integer是Number的子類,一個Integer對象也是一個Number對象好啰,所以一個Integer的數(shù)組也是一個Number的數(shù)組轩娶,這就是數(shù)組的協(xié)變。
Java把數(shù)組設(shè)計成協(xié)變的框往,在一定程度上是有缺陷的鳄抒。因為盡管把Integer[]賦值給Number[],Integer[]可以向上轉(zhuǎn)型為Number[]椰弊,但是數(shù)據(jù)元素的實際類型是Integer许溅,只能向數(shù)組中放入Integer或者Integer的子類。如果向數(shù)組中放入Number對象或者Number其他子類的對象秉版,對于編譯器來說也是可以通過編譯的贤重。但是運行時JVM能夠知道數(shù)組元素的實際類型是Integer,當(dāng)其它對象加入數(shù)組是就會拋出異常(java.lang.ArrayStoreException)清焕。
泛型的設(shè)計目的之一就是保證了類型安全并蝗,讓這種運行時期的錯誤在編譯期就能發(fā)現(xiàn),所以泛型是不支持協(xié)變的秸妥。例如:
List<Number> nums = new ArrayList<Integer>(); // incompatible types
當(dāng)確實需要建立這種向上轉(zhuǎn)型的類型關(guān)系的時候滚停,就需要用到泛型的通配符特性了。例如:
List<? extends Number> nums = new ArrayList<Integer>(); // OK
無邊界通配符(Unbounded Wildcards)
語法:
class-name<?> var-name
例子:
public static void print(List<?> list) {
for (Object obj : list) {
System.out.println(o);
}
}
List<?> list和List list的區(qū)別:
- List<?> list是表示持有某種特定類型對象的List粥惧,但是不知道是哪種類型键畴;List list是表示持有Object類型對象的List。
- List<?> list因為不知道持有的實際類型突雪,所以不能add任何類型的對象起惕,但是List list因為持有的是Object類型對象,所以可以add任何類型的對象咏删。
注意:List<?> list可以add(null)惹想,因為null是任何引用數(shù)據(jù)類型都具有的元素。
上邊界限定的通配符(Upper Bounded Wildcards)
語法:
class-name<? extends superclass> var-name
例子:
public static double sum(List<? extends Number> list) {
double s = 0.0;
for (Number num : list) {
s += num.doubleValue();
}
return s;
}
List<? extends Number> list = new ArrayList<Integer>(); // OK
List<? extends Number> list = new ArrayList<Object>(); // error
特性:
- List<? extends Number> list表示某種特定類型(Number或者Number的子類)對象的List督函。跟無邊界通配符一樣勺馆,因為無法確定持有的實際類型,所以這個List也不能add除null外的任何類型的對象侨核。
list.add(new Integer(1)); // error
list.add(null); // OK
- 從list中獲取對象是是可以的(比如get(0)),因為在這個List中灌灾,不管實際類型是什么搓译,但肯定都能轉(zhuǎn)型為Number。
Number n = list.get(0); // OK
Integer i = list.get(0); // error
- 事實上锋喜,只要是形式參數(shù)有使用類型參數(shù)的方法些己,在使用無邊界或者上邊界限定的通配符的情況下豌鸡,都不能調(diào)用。比如以java.util.ArrayList為例:
public E get(int index) // 可以調(diào)用
public int indexOf(Object o) // 可以調(diào)用
public boolean add(E e) // 不能調(diào)用
下邊界限定的通配符(Lower Bounded wildcards)
語法:
class-name<? super subclass> var-name
例子:
public static void writeTo(List<? super Integer> list) {
// ...
}
List<? super Number> list = new ArrayList<Number>(); // OK
List<? super Number> list = new ArrayList<Object>(); // OK
List<? super Number> list = new ArrayList<Integer>(); // error
特性:
- List<? super Integer> list表示某種特定類型(Integer或者Integer的父類)對象的List段标⊙墓冢可以確定這個List持有的對象類型肯定是Integer或者其父類,所以往list里面add一個Integer或者其子類的對象是安全的逼庞,因為Integer或者其子類的對象都可以向上轉(zhuǎn)型為Integer的父類對象蛇更。但是因為無法確定實際類型,所以往list里面add一個Integer的父類對象是不安全的赛糟。
list.add(new Integer(1)); // OK
list.add(new Object()); // error
- 當(dāng)從List<? super Integer> list獲取具體的數(shù)據(jù)的時候派任,JVM在編譯的時候知道實際類型可以是任何Integer的父類,所以為了安全起見璧南,要用一個最頂層的父類對象來指向取出的數(shù)據(jù)掌逛,這樣就可以避免發(fā)生強制類型轉(zhuǎn)換異常了。
Object obj = list.get(0); // OK
Integer i = list.get(0); // error
PECS原則(Producer Extends Consumer Super)
從上面上邊界限定的通配符和下邊界限定的通配符的特性司倚,可以知道:
- 對于上邊界限定的通配符豆混,無法向其中加入任何對象,但是可以從中正常取出對象动知。
- 對于下邊界限定的通配符皿伺,可以存入subclass對象或者subclass的子類對象,但是取出時只能用Object類型變量指向取出的對象拍柒。
簡而言之心傀,上邊界限定(extends)的通配符適合于內(nèi)容的獲取,而下邊界限定(super)的通配符更適合于內(nèi)容的存入拆讯。所以就有了一個PECS原則來很好的解釋這兩種通配符的使用原則脂男。
- 當(dāng)一個數(shù)據(jù)結(jié)構(gòu)作為producer對外提供數(shù)據(jù)的時候,應(yīng)該只能取數(shù)據(jù)而不能存數(shù)據(jù)种呐,所以適合使用上邊界限定(extends)的通配符宰翅。
- 當(dāng)一個數(shù)據(jù)結(jié)構(gòu)作為consumer獲取并存入數(shù)據(jù)的時候,應(yīng)該只能存數(shù)據(jù)而不能取數(shù)據(jù)爽室,所以適合使用下邊界限定(super)的通配符汁讼。
- 如果既需要取數(shù)據(jù)也需要存數(shù)據(jù),就不適合使用泛型的通配符阔墩。
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
dest.set(i, src.get(i));
}
}