無(wú)界通配符
無(wú)界通配符<?> 看起來(lái)意味著“任何事物”,因此使用無(wú)界通配符好像等價(jià)于使用原生類(lèi)型。而事實(shí)上,編譯器初看起來(lái)是支持這種判斷的珍德。
public class UnBoundedWildcards {
static List list1;
static List<?> list2;
static List<? extends Object> list3;
static void assign1(List list) {
list1 = list;
list2 = list;
// warning: unchecked conversion
// Found: List, Require: List<? extends Object>
list3 = list;
}
static void assign2(List<?> list) {
list1 = list;
list2 = list;
list3 = list;
}
static void assign3(List<? extends Object> list) {
list1 = list;
list2 = list;
list3 = list;
}
public static void main(String[] args) {
assign1(new ArrayList());
assign2(new ArrayList());
// warning: unchecked conversion
// Found: ArrayList, Require: List<? extends Object>
assign3(new ArrayList());
assign1(new ArrayList<String>());
assign2(new ArrayList<String>());
assign3(new ArrayList<String>());
List<?> wildList = new ArrayList();
assign1(wildList);
assign2(wildList);
assign3(wildList);
}
}
編譯器很少關(guān)心使用的是原生類(lèi)型還是<?>
,而在這種情況下矗漾,<?>
可以被認(rèn)為是一種裝飾锈候,但是它仍舊是很有價(jià)值的,因?yàn)槌ü保瑢?shí)際上泵琳,它是在聲明:“想用Java的泛型來(lái)編寫(xiě)代碼,但是我在這里并不是要用原生類(lèi)型誊役,但是在當(dāng)前這種情況下获列,泛型參數(shù)可以持有任何類(lèi)型』坠福”
下面的示例展示了無(wú)界通配符的一個(gè)重要的應(yīng)用击孩。當(dāng)你處理多個(gè)泛型參數(shù)的時(shí),有時(shí)允許一個(gè)參數(shù)可以是任何的類(lèi)型鹏漆,同時(shí)為其他參數(shù)確定某種特定類(lèi)型的能力會(huì)顯得尤為重要巩梢。
public class UnBoundedWildcards2 {
static Map map1;
static Map<?, ?> map2;
static Map<String, ?> map3;
static void assign1(Map map) {
map1 = map;
}
static void assign2(Map<?, ?> map) {
map2 = map;
}
static void assign3(Map<String, ?> map) {
map3 = map;
}
public static void main(String[] args) {
assign1(new HashMap());
assign2(new HashMap());
// warning
// Unchecked assignment: 'java.util.HashMap' to 'java.util.Map<java.lang.String,?>'
assign3(new HashMap());
assign1(new HashMap<String, Integer>());
assign2(new HashMap<String, Integer>());
assign3(new HashMap<String, Integer>());
}
}
但是,當(dāng)擁有的全都是無(wú)界通配符的時(shí)候艺玲,就像在Map<?,?>
中看到的那樣括蝠,編譯器看起來(lái)無(wú)法將其與原生Map
區(qū)分開(kāi)了。而另外UnBoundedWildcards.java
中也展示了編譯器處理List<?>
和List<? extends Object>
時(shí)是不同的饭聚。
而令人困惑的是忌警,編譯器并非總是關(guān)注像List
和List<?>
之間的差異,因此它們看起來(lái)就像是相同的事物秒梳。事實(shí)上法绵,由于泛型參數(shù)將擦除到它的第一個(gè)邊界箕速,因此List<?>
看起來(lái)就等價(jià)于List<Object>
,而List
實(shí)際上也是List<Object>
礼烈。List
實(shí)際上是表示“持有任何Object
類(lèi)型的原生List
”弧满,而List<?>
表示“具有某種特定類(lèi)型”的非原生List婆跑,并且我們不知道哪個(gè)類(lèi)型是什么此熬。
那么問(wèn)題來(lái)了,編譯器什么時(shí)候才會(huì)去關(guān)注原生類(lèi)型和涉及無(wú)界通配符的類(lèi)型之間的差異呢滑进?下面的示例使用了之前定義的Holder<T>
類(lèi)犀忱,它包含接受Holder
作為參數(shù)的各種方法,但是它們具有不同的形式扶关,作為原生類(lèi)型阴汇,具有具體的類(lèi)型參數(shù)以及具有無(wú)界通配符參數(shù)。
public class Wildcards {
static void rawArgs(Holder holder, Object arg) {
// unchecked warning
// holder.set(arg);
// same warning
// holder.set(new Wildcards());
// compile error, don't have any T
// T t = holder.get();
// No warning, but type information has been lost
Object obj = holder.get();
}
// 和rawArgs相似节槐,但是其中的warning會(huì)變成編譯錯(cuò)誤
static void unboundedArg(Holder<?> holder, Object arg) {
// Compile Error set(capture of ?) but accept Object
// holder.set(arg);
// same error
// holder.set(new Wildcards());
// compile error, don't have any T
// T t = holder.get();
// No warning, but type information has been lost
Object obj = holder.get();
}
static <T> T exact1(Holder<T> holder) {
T t = holder.get();
return t;
}
static <T> T exact2(Holder<T> holder, T arg) {
holder.set(arg);
T t = holder.get();
return t;
}
static <T> T wildSubtype(Holder<? extends T> holder, T arg) {
// Error
// set (capture<? extends T>) in Holder cannot be applied to (T)
// holder.set(arg);
T t = holder.get();
return t;
}
static <T> void wildSupertype(Holder<? super T> holder, T arg) {
holder.set(arg);
// Error
// Required: T Found: capture<? super T>
// T t = holder.get();
// No warning, but type information has been lost
Object obj = holder.get();
}
public static void main(String[] args) {
Holder raw = new Holder<Long>();
// Or
raw = new Holder();
Holder<Long> qualified = new Holder<>();
Holder<?> unbounded = new Holder<Long>();
Holder<? extends Long> bounded = new Holder<Long>();
Long lng = 1L;
rawArgs(raw, lng);
rawArgs(qualified, lng);
rawArgs(unbounded, lng);
rawArgs(bounded, lng);
unboundedArg(raw, lng);
unboundedArg(qualified, lng);
unboundedArg(unbounded, lng);
unboundedArg(bounded, lng);
// unchecked warning
Object r1 = exact1(raw);
Long r2 = exact1(qualified);
Object r3 = exact1(unbounded);
Long r4 = exact1(bounded);
// unchecked warning
Long r5 = exact2(raw, lng);
Long r6 = exact2(qualified, lng);
// Error 參數(shù)錯(cuò)誤
// Long r7 = exact2(unbounded, lng);
// Error 參數(shù)錯(cuò)誤
// Long r8 = exact2(bounded, lng);
// unchecked warning
Long r9 = wildSubtype(raw, lng);
Long r10 = wildSubtype(qualified, lng);
Object r11 = wildSubtype(unbounded, lng);
Long r12 = wildSubtype(bounded, lng);
// unchecked warning
wildSupertype(raw, lng);
wildSupertype(qualified,lng);
// Error 參數(shù)錯(cuò)誤
// wildSupertype(unbounded,lng);
// wildSupertype(bounded,lng);
}
}
在rawArgs()
中搀庶,編譯器知道Holder
是一種泛型類(lèi)型,因此即使它在這里被表示為一個(gè)原生類(lèi)型铜异,編譯器仍舊知道向set()
傳遞一個(gè)Object
是不安全的哥倔。由于它是原生類(lèi)型,可以將任何類(lèi)型的對(duì)象傳遞給set()
揍庄,而這個(gè)對(duì)象將向上轉(zhuǎn)型為Object
咆蒿。因此,無(wú)論何時(shí)蚂子,只要使用了原生類(lèi)型沃测,都會(huì)放棄編譯器檢查。而對(duì)get()
的調(diào)用說(shuō)明了相同的問(wèn)題:沒(méi)有T
類(lèi)型的對(duì)象食茎,因此結(jié)果只能是一個(gè)Object
蒂破。
人們很自然地會(huì)開(kāi)始考慮原生Holder
和Holder<?>
是大致相同的事物。但是uboundedArg()
強(qiáng)調(diào)它們是不同的——它揭示了相同的問(wèn)題别渔,但是它將這些問(wèn)題作為錯(cuò)誤而不是警告報(bào)告附迷,因?yàn)樵?code>Holder將持有任何類(lèi)型的組合各薇,而Holder<?>
將持有某種具體類(lèi)型的同構(gòu)集合饮睬,因此不能只是向其中傳遞Object
呕屎。
在exact1()
和exact2()
中坏瞄,都使用了確切的泛型參數(shù)——沒(méi)有任何的通配符窗市。而exact2()
比exact1()
多了一個(gè)額外的參數(shù)脆荷。
在wildSubType()
中蚌成,在Holder
類(lèi)型上的限制被放松為持有任何擴(kuò)展至T
對(duì)象的Holder
芭析,這還是意味著如果T
是Fruit
截珍,那么holder
可以是Holder<Apple>
攀甚。而為了防止將Orange
放置到Holder<Apple>
中箩朴,對(duì)set()
的調(diào)用(或者對(duì)任何接受這個(gè)類(lèi)型參數(shù)為參數(shù)的方法的調(diào)用)都是不允許的。但是秋度,你仍然知道任何來(lái)自Holder<? extends Fruit>
的對(duì)象至少是Fruit
炸庞,因此get()
(或者任何將產(chǎn)生具有這個(gè)類(lèi)型參數(shù)的返回值的方法)都是允許的。
wildSupertype()
展示了超類(lèi)型通配符荚斯,這個(gè)方法展示了和wildSubtype()
相反的行為:holder
可以是持有任何T
的基類(lèi)型的容器埠居,所以,set
可以接受T
事期,因?yàn)榭梢怨ぷ饔诨?lèi)的對(duì)象都可以多態(tài)地作于導(dǎo)出類(lèi)(這里指的是T
)滥壕。但是,嘗試著調(diào)用get()
是沒(méi)有用的兽泣,因?yàn)橛?code>holder持有的類(lèi)型是任何超類(lèi)型绎橘,唯一安全的是Object
。
這個(gè)示例還展示了對(duì)于unbounded()
中使用無(wú)界通配符能夠做不能上嗎做什么所做出的限制唠倦。對(duì)于遷移兼容性称鳞,rawArgs()
將接受所有Holder
的不同變體,而不會(huì)產(chǎn)生警告稠鼻。unboundedArg()
方法也可以接受相同的所有類(lèi)型冈止,盡管如前所述,它在方法體內(nèi)部處理這些類(lèi)型的防暑并不相同枷餐。
如果向接受“確切”泛型類(lèi)型(沒(méi)有通配符)的方法傳遞一個(gè)原生Holder
引用靶瘸,就會(huì)得到一個(gè)警告,因?yàn)榇_切的參數(shù)期望得到在原生類(lèi)型中并不存在的信息毛肋。如果向exact1()
傳遞一個(gè)一個(gè)無(wú)界引用怨咪,就不會(huì)有任何可以確定返回類(lèi)型的信息。
而可以看到润匙,exact2()
具有最多的限制诗眨,因?yàn)樗M_地得到一個(gè)Holder<T>
,以及一個(gè)具有類(lèi)型T
的參數(shù)孕讳,正因?yàn)槿绱私吵鼘a(chǎn)生錯(cuò)誤或者警告,除非提供確切的參數(shù)厂财。雖然有時(shí)這樣做很好芋簿,但是如果它過(guò)于受限,那么就可以使用通配符璃饱,這取決于是否想從泛型參數(shù)中返回類(lèi)型確定的返回值(就像在wildSubtype
中看到的那樣)与斤,或者是否想要向泛型參數(shù)傳遞類(lèi)型確定的參數(shù)(就像在wildSupertype()
中看到那樣)。
因此,使用確切類(lèi)型來(lái)替代通配符的好處是撩穿,可以用泛型參數(shù)來(lái)做更多的事磷支,但是使用通配符使得必須接受范圍更寬的參數(shù)化類(lèi)型作為參數(shù)。因此必須權(quán)衡利弊食寡,找到適合的方法雾狈。