之前一直對(duì)Java泛型中的通配符不是很清楚,前幾天專(zhuān)門(mén)研究了一下珊膜。
Java中的泛型通配符分為以下三種:
- <? extends T> 子類(lèi)型限定通配符
- <? super T> 超類(lèi)型限定通配符
- <?> 無(wú)限定通配符
通配符的使用場(chǎng)景
通配符只有在修飾一個(gè)變量或參數(shù)的時(shí)候會(huì)用到容握,在定義泛型類(lèi)或泛型方法的時(shí)候是不能使用通配符的。
為了更好的說(shuō)明泛型通配符的使用车柠,我們使用代碼示例來(lái)加以說(shuō)明剔氏。首先我們創(chuàng)建一個(gè)類(lèi) A,是一個(gè)泛型類(lèi)竹祷,里面保存一個(gè)變量 value
public class A<T> {
private T value;
// 省略 get 和 set 方法
// ......
}
我們?cè)賱?chuàng)建兩個(gè)類(lèi) Father 和 Son谈跛,Son 是 Father 的子類(lèi)
// Father.java
public class Father {
}
// Son.java
public class Son extends Father{
}
思考下面的情況
public static void main(String[] args){
A<Father> a1 = new A<Father>();
A<Son> a2 = new A<Son>();
test(a1);
test(a2); // 編譯錯(cuò)誤
}
public void test(A<Father> a){...}
Son 是 Father 的子類(lèi),但 test(A<Father> a)
方法卻不能接收參數(shù) a2塑陵,也就是說(shuō) A<Son> 不是 A<Father> 的子類(lèi)感憾。這個(gè)時(shí)候就可以使用通配符來(lái)解決,我們修改 test 方法
public static void main(String[] args){
A<Father> a1 = new A<Father>();
A<Son> a2 = new A<Son>();
test(a1);
test(a2);
}
public void test(A<? extends Father> a){...}
這樣可以可以正常調(diào)用猿妈,說(shuō)明類(lèi)型 A<Son>
是 A<? extends Father>
的子類(lèi)型
我們清楚了通配符的使用場(chǎng)景吹菱,下面再分別看下幾種通配符
<? extends T> 子類(lèi)型限定通配符
我們上面的例子使用了子類(lèi)型限定通配符,使用通配符很方便彭则,但也帶來(lái)了一些問(wèn)題鳍刷,我們接著看代碼
public void test(A<? extends Father> a){
a.setValue(new Father()); //編譯錯(cuò)誤
a.setValue(new Son()); //編譯錯(cuò)誤
Father father = a.getValue();
}
你會(huì)發(fā)現(xiàn)我們根本不能調(diào)用 setValue 方法,假想一下 A<? extends Father> 類(lèi)俯抖,里面的方法似乎是這樣的
// 這不是真正的Java方法输瓜,只是為了說(shuō)明
? extends Father getValue();
void setValue(? extends Father);
當(dāng)我們調(diào)用 setValue 方法的時(shí)候,編譯器只知道需要某個(gè) Father 及其子類(lèi)型,但不知道具體是什么類(lèi)型尤揣,所以它拒絕傳遞任何的特定類(lèi)型搔啊。
使用 getValue 就不會(huì)有問(wèn)題,因?yàn)榉祷刂悼隙ㄊ?Father 及其子類(lèi)型北戏,所以我們把返回值賦給一個(gè) Father 的引用完全合法负芋。
<? super T> 超類(lèi)型限定通配符
超類(lèi)型限定通配符的行為與上面說(shuō)的子類(lèi)型限定通配符相反,可以為方法提供參數(shù)嗜愈,但不能使用返回值旧蛾。我們看下面的例子:
public void test2(A<? super Father> a){
a.setValue(new Father());
Father father = a.getValue(); // 編譯錯(cuò)誤
Object object = a.getValue();
}
假想一下 A<? super Father> 類(lèi),里面的方法似乎是這樣的
// 這不是真正的Java方法蠕嫁,只是為了說(shuō)明
void setValue(? super Father)
? super Father getValue()
setValue 方法不知道參數(shù)的具體類(lèi)型锨天,但是可以確定的是參數(shù)肯定是 Father 及其父類(lèi)型,所以我們傳遞 Father 及其子類(lèi)型是合法的剃毒。
調(diào)用 getValue 方法不能保證返回類(lèi)型的對(duì)象病袄,所以只能賦給一個(gè) Object。
<?> 無(wú)限定通配符
還可以使用無(wú)限定通配符 <?> 赘阀,這種方式益缠,不能為方法提供參數(shù),調(diào)用方法返回值也只能賦給 Object纤壁。
public void test3(A<?> a){
Object object = a.getValue();
a.setValue(new Object()); //編譯錯(cuò)誤
}
getValue 的返回值只能賦給一個(gè) Object左刽,setValue 方法不能被調(diào)用(注意:可以調(diào)用 setValue(null))。
所以感覺(jué)無(wú)限定通配符是集合了上面說(shuō)的兩種通配符的缺點(diǎn)酌媒,那我們?yōu)槭裁催€要使用它呢欠痴?其實(shí)在某些簡(jiǎn)單的場(chǎng)景還是有用的,例如下面這種情況
// 判斷A類(lèi)中的值是否為空秒咨,并不關(guān)心A類(lèi)中值具體是什么類(lèi)型
public Boolean isNull(A<?> a){
return a.getValue == null;
}
總結(jié)
看完了三種通配符的使用喇辽,我們來(lái)做個(gè)總結(jié):
- <? extends T> 子類(lèi)型限定通配符
無(wú)法向其中設(shè)置值,但是可以進(jìn)行正常的取出 - <? super T> 父類(lèi)型限定通配符
可以設(shè)置 T 類(lèi)型及其子類(lèi)型的對(duì)象雨席,但是取出的時(shí)候只能賦值給 Object - <?> 無(wú)限定通配符
無(wú)法向其中設(shè)置值菩咨,取值的時(shí)候也只能賦值給 Object
從上面的總結(jié)可以看出,<? extends T> 通配符偏向于內(nèi)容的獲取陡厘,而 <? super T> 通配符更偏向于內(nèi)容的存入抽米。
PECS 原則(Producer Extends Consumer Super)很好的解釋了這兩種通配符的使用場(chǎng)景:
- Producer Extends 說(shuō)的是當(dāng)你的情景是生產(chǎn)者類(lèi)型,需要獲取資源以供生產(chǎn)時(shí)糙置,建議使用 extends 通配符云茸,因?yàn)槭褂昧?extends 通配符的類(lèi)型更適合獲取資源。
- Consumer Super 說(shuō)的是當(dāng)你的場(chǎng)景是消費(fèi)者類(lèi)型谤饭,需要存入資源以供消費(fèi)時(shí)标捺,建議使用 super 通配符懊纳,因?yàn)槭褂?super 通配符的類(lèi)型更適合存入資源。
當(dāng)然亡容,如果你既想設(shè)置值又想取出值嗤疯,那么就不適合使用通配符了。