Java泛型中的通配符舉例詳解
前言
在泛型代碼中,問(wèn)號(hào)(?)稱為通配符,用來(lái)表示未知類型。通配符可以在多種情況下使用:如作為參數(shù)、字段或局部變量的類型;有時(shí)也可以作為返回類型。另外,通配符永遠(yuǎn)不會(huì)用作調(diào)用泛型方法、創(chuàng)建泛型類或超類型實(shí)例的類型參數(shù)。
為什么使用通配符?
在 Java 中,類和數(shù)組之間的對(duì)象關(guān)系是可以繼承的,比如 Dog extends Animal,那么 Animal[] 與 Dog[] 就是兼容的。但是集合之間卻不存在這種關(guān)系,也就是說(shuō) List<Animal> 不是List<Dog> 的父類,他們之間沒(méi)有任何關(guān)系。那么為了建立兩個(gè)集合之間的聯(lián)系,就需要用到通配符。// 只是其中一部分原因
1、如何定義和使用上界通配符?
可以使用上界通配符來(lái)放寬對(duì)變量的限制。例如,你想編寫(xiě)一個(gè)方法,該方法適用于List<Integer>, List<Double> 和 List<Number>;就可以通過(guò)使用上界通配符來(lái)實(shí)現(xiàn)這一點(diǎn)。
聲明一個(gè)上界通配符,需要使用通配符 ('?'),跟上 extends 關(guān)鍵字,然后再跟它的上界。比如,編寫(xiě)用于 Number 列表和 Number 子類型(如 Integer、Double 和 Float)的方法,可以指定 List<? extends Number>。List<? extends Number> 要比 List<Number> 對(duì)類型的限制更加寬松,因?yàn)?List<Number> 只匹配類型為 Number 的列表,而 List<? extends Number> 匹配類型為 Number 或其任何子類的列表。
例如下邊的程序代碼:
public static void process(List<? extends Foo> list) { /* ... */ }上界通配符 <? extends Foo> 中的 Foo 匹配 Foo 類型和 Foo 的任何子類型。process() 方法可以訪問(wèn)類型為 Foo 的列表元素:
public static void process(List<? extends Foo> list) {
for (Foo elem : list) {
// ...
}
}在 foreach 子句中,elem 變量迭代列表中的每個(gè)元素。在 elem 元素上可以使用 Foo 類中定義的任何方法。
如下,sumOfList() 方法返回列表中數(shù)字的和:
public static double sumOfList(List<? extends Number> list) {
double s = 0.0;
for (Number n : list)
s += n.doubleValue();
return s;
}下面的代碼,使用一個(gè) Integer 對(duì)象列表,輸出 sum = 6.0:
List<Integer> li = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sumOfList(li));Double 值列表可以使用相同的 sumOfList() 方法。下面的代碼輸出 sum = 7.0:
List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sumOfList(ld));2、如何定義和使用無(wú)界通配符?
無(wú)界通配符類型使用通配符(?)指定,例如 List<?>。在下邊兩種情況下,無(wú)界通配符是一種有用的方法:
- 編寫(xiě)一個(gè)方法,該方法可以使用 Object 類中提供的功能函數(shù)。
- 代碼在泛型類中使用不依賴于類型參數(shù)的方法。例如,List.size 或 List.clear。事實(shí)上,Class<?> 之所以如此常用,是因?yàn)?Class<T> 中的大多數(shù)方法都不依賴于 T。
例如以下 printList() 方法:
public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}printList() 的目標(biāo)是用來(lái)打印任何類型的列表,但上述代碼中它只能打印 Object 實(shí)例的列表;并不能打印 List<Integer>, List<String>, List<Double> 等,因?yàn)檫@些列表不是 List<Object> 的子類型。所以如果要編寫(xiě)一個(gè)通用的 printList() 方法,需要使用到 List<?>:
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}對(duì)于任何具體的類型 A,List<A> 都是 List<?> 的子類,可以使用 printList() 打印任何類型的列表:// 解決了之前 List<A> 和 List<B> 無(wú)任何關(guān)系的問(wèn)題
List<Integer> li = Arrays.asList(1, 2, 3);
List<String> ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);注意:List<Object> 和 List<?> 是不一樣的??梢詫?Object 或 Object 的任何子類型插入到 List<Object> 中,但是只能將 null 插入到 List<?> 中。
3、如何定義和使用下界通配符?
上界通配符將未知類型限制為特定類型的子類型,使用 extends 關(guān)鍵字表示。類似地,下界通配符將未知類型限制為特定類型的超類型,使用 super 關(guān)鍵字表示。
下界通配符使用通配符('?')表示,后面跟著 super 關(guān)鍵字,然后再跟著它的下界:<? super A>。
注意:可以為通配符指定上界,也可以指定下界,但不能同時(shí)都指定。
例如,編寫(xiě)一個(gè)將 Integer 對(duì)象放入列表的方法。為了最大限度地提高靈活性,該方法需要適用于 List<Integer>、List<Number> 和 List<Object> 等任何可以保存 Integer 值的對(duì)象。那么要編寫(xiě)處理 Integer 列表和 Integer 超類型列表的方法,就可以使用 List<? super Integer> 來(lái)指定。如以下代碼將數(shù)字 1 到 10 添加到列表的末尾:
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}4、如何使用通配符定義泛型類或接口之間的子類型關(guān)系?
泛型類或接口之間的關(guān)聯(lián)并不取決于它們的類型之間是否存在關(guān)聯(lián)。不過(guò),我們可以使用通配符來(lái)創(chuàng)建泛型類或接口之間的關(guān)聯(lián)關(guān)系。
給定以下兩個(gè)非泛型類:
class A { /* ... */ }
class B extends A { /* ... */ }編寫(xiě)以下代碼是合理的:
B b = new B(); A a = b;
上邊這個(gè)例子表明常規(guī)類的繼承遵循子類型的規(guī)則:如果 B 繼承 A,那么 B.calss 就是 A.calss 的子類型。但是這個(gè)規(guī)則并不適用于泛型類型:
List<B> lb = new ArrayList<>(); List<A> la = lb; // compile-time error
假設(shè) Integer 是 Number 的子類型,那么 List<Integer> 和 List<Number> 之間的關(guān)系是什么呢?

雖然 Integer 是 Number 的子類型,但 List<Integer> 并不是 List<Number> 的子類型,所以,這兩種類型并不相關(guān)。事實(shí)上,List<Number> 和 List<Integer> 的公共父類是 List<?>。
為了在這些類之間創(chuàng)建關(guān)系,讓代碼可以通過(guò) List<Integer> 的元素訪問(wèn) Number 的方法,可以使用一個(gè)上界通配符:
List<? extends Integer> intList = new ArrayList<>(); List<? extends Number> numList = intList; // OK. List<? extends Integer> is a subtype of List<? extends Number>
因?yàn)?Integer 是 Number 的子類型,而 numList 是 Number 對(duì)象的列表,所以 intList (Integer對(duì)象的列表)和 numList 之間存在關(guān)聯(lián)關(guān)系。下圖顯示了用上下界通配符聲明的幾個(gè) List 類之間的關(guān)系。// 使用通配符可以定義兩個(gè)類型之間的關(guān)系

5、通配符的捕獲和輔助方法
在某些情況下,編譯器會(huì)自動(dòng)推斷通配符的類型。例如,列表可以定義為 List<?>,但是,當(dāng)計(jì)算表達(dá)式時(shí),編譯器會(huì)從代碼中推斷出特定的類型,這種場(chǎng)景稱為通配符捕獲。
大多數(shù)情況下,都不需要擔(dān)心通配符的捕獲,除非看到包含短語(yǔ) “capture of” 的錯(cuò)誤消息。
如下,WildcardError 示例會(huì)在編譯時(shí)會(huì)產(chǎn)生一個(gè)捕獲錯(cuò)誤:
import java.util.List;
public class WildcardError {
void foo(List<?> i) {
i.set(0, i.get(0));
}
}在本例中,編譯器將 i 輸入形參處理為 Object 類型。當(dāng) foo 方法調(diào)用 List.set(int, E) 時(shí),編譯器無(wú)法確認(rèn)插入到列表中的對(duì)象類型,所以會(huì)產(chǎn)生錯(cuò)誤。當(dāng)發(fā)生這種類型的錯(cuò)誤時(shí),通常意味著編譯器認(rèn)為給變量分配了錯(cuò)誤的類型。將泛型添加到 Java 語(yǔ)言中就是出于這個(gè)原因——在編譯時(shí)加強(qiáng)類型安全。
當(dāng)使用 Oracle 的 JDK 7 javac 實(shí)現(xiàn)編譯時(shí),WildcardError 示例會(huì)生成以下錯(cuò)誤:
WildcardError.java:6: error: method set in interface List<E> cannot be applied to given types;
i.set(0, i.get(0));
^
required: int,CAP#1
found: int,Object
reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
where E is a type-variable:
E extends Object declared in interface List
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
1 error
在本例中,代碼試圖執(zhí)行安全操作,那么如何解決編譯器錯(cuò)誤呢?可以通過(guò)編寫(xiě)一個(gè)捕獲通配符的私有 helper 方法來(lái)修復(fù)它。如 WildcardFixed 所示,通過(guò)創(chuàng)建私有輔助方法 fooHelper 來(lái)解決這個(gè)問(wèn)題:
public class WildcardFixed {
void foo(List<?> i) {
fooHelper(i);
}
// 創(chuàng)建輔助方法,以便可以通過(guò)類型推斷捕獲通配符
private <T> void fooHelper(List<T> l) {
l.set(0, l.get(0));
}
}由于使用了 helper 方法,編譯器使用推斷來(lái)確定調(diào)用中的 T 是 CAP#1,即捕獲變量。示例現(xiàn)在編譯成功。
6、通配符使用指南
在使用泛型進(jìn)行編程時(shí),讓人比較困惑的是需要確定何時(shí)使用上界通配符,何時(shí)使用下界通配符。本節(jié)提供了一些設(shè)計(jì)代碼時(shí)要遵循的指導(dǎo)原則。// 以下是來(lái)自官方的指導(dǎo)原則
為了方便理解,可以將變量看作以下兩種形式:
- “in” 變量:“in” 變量為代碼提供數(shù)據(jù)。比如一個(gè)復(fù)制方法有兩個(gè)參數(shù):copy(src, dest)。src 參數(shù)提供了需要復(fù)制的數(shù)據(jù),因此它是 “in” 形參。
- “out” 變量:“out” 變量用來(lái)保存輸出數(shù)據(jù),便于該數(shù)據(jù)在其他地方使用。在 copy(src, dest) 方法中, dest 參數(shù)用來(lái)接收數(shù)據(jù),因此它是 "out" 形參。
當(dāng)然,有些變量同時(shí)用于 “輸入” 和 “輸出” 目的,下邊指南中也提到了這種情況。
在決定是否使用通配符以及使用什么類型的通配符時(shí),可以使用 “in” 和 “out” 原則。以下列表提供了需要遵循的指導(dǎo)方針:
- 對(duì)于 “in” 變量,可以定義一個(gè)上界通配符,使用 extends 關(guān)鍵字。
- 對(duì)于“out”變量,可以定義一個(gè)下界通配符,使用 super 關(guān)鍵字。
- 如果 “in” 變量可以使用 Object.calss 中定義的方法對(duì)其進(jìn)行訪問(wèn),可以使用無(wú)界通配符。
- 如果變量同時(shí)用于 “輸入” 和 “輸出”的情況下,不要使用通配符。// 限定唯一的類型
以上這些準(zhǔn)則不適用于方法的返回類型。應(yīng)該避免使用通配符作為方法的返回類型,因?yàn)樗鼤?huì)迫使使用代碼的程序員去處理通配符。
List<? extends ...> 可以認(rèn)為它是只讀的,但并不能進(jìn)行嚴(yán)格的保證,假設(shè)有以下兩個(gè)類:
class NaturalNumber {
private int i;
public NaturalNumber(int i) { this.i = i; }
// ...
}
class EvenNumber extends NaturalNumber {
public EvenNumber(int i) { super(i); }
// ...
}考慮下面的代碼:
List<EvenNumber> le = new ArrayList<>(); List<? extends NaturalNumber> ln = le; ln.add(new NaturalNumber(35)); // 編譯錯(cuò)誤
因?yàn)?List<EvenNumber> 是 List<? extends NaturalNumber> 的子類型,所以可以將 le 賦值給 ln。但是卻不能用 ln 把一個(gè)自然數(shù)加到一個(gè)偶數(shù)列表中。在該列表中只可以進(jìn)行以下操作:
- 添加 null 元素。
- 調(diào)用 clear() 方法。
- 獲取迭代器并調(diào)用 remove() 方法。
- 捕獲通配符并寫(xiě)入從列表中讀取的元素。
所以,從嚴(yán)格意義上來(lái)講,List<? extends NaturalNumber> 并不是只讀的,但是卻可以認(rèn)為它是只讀的,因?yàn)椴荒茉诹斜碇写鎯?chǔ)新的元素或更改現(xiàn)有元素。
總結(jié)
到此這篇關(guān)于Java泛型中的通配符的文章就介紹到這了,更多相關(guān)Java泛型的通配符內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java版仿QQ驗(yàn)證碼風(fēng)格圖片驗(yàn)證碼
這篇文章主要為大家分享了java圖片驗(yàn)證碼實(shí)例代碼,感興趣的小伙伴們可以參考一下2016-04-04
解決shiro 定時(shí)監(jiān)聽(tīng)器不生效的問(wèn)題 onExpiration不調(diào)用問(wèn)題
這篇文章主要介紹了解決shiro 定時(shí)監(jiān)聽(tīng)器不生效的問(wèn)題 onExpiration不調(diào)用問(wèn)題。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
java拼接字符串時(shí)去掉最后一個(gè)多余逗號(hào)的方法
這篇文章主要介紹了java拼接字符串時(shí)去掉最后一個(gè)多余逗號(hào)的方法,實(shí)例分析了java操作字符串的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03
SpringMVC九大組件之HandlerMapping詳解
這篇文章主要介紹了SpringMVC九大組件之HandlerMapping詳解,HandlerMapping 叫做處理器映射器,它的作用就是根據(jù)當(dāng)前 request 找到對(duì)應(yīng)的 Handler 和 Interceptor,然后封裝成一個(gè) HandlerExecutionChain 對(duì)象返回,需要的朋友可以參考下2023-09-09
Java本地方法(JNA)詳解及常見(jiàn)問(wèn)題
JNA(Java?Native?Access)是一個(gè)開(kāi)源Java框架,用于無(wú)需編寫(xiě)JNI代碼即可動(dòng)態(tài)訪問(wèn)本地系統(tǒng)庫(kù)如Windows的dll,它允許Java程序直接調(diào)用本地方法,這篇文章主要介紹了Java本地方法(JNA)詳解及常見(jiàn)問(wèn)題,需要的朋友可以參考下2024-09-09
spring中jdbcTemplate.batchUpdate的幾種使用情況
本文主要介紹了spring中jdbcTemplate.batchUpdate的幾種使用情況,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
java中Spring事務(wù)失效的8個(gè)常見(jiàn)案例和解決方案
本文分析了8種Spring事務(wù)失效問(wèn)題及解決方案,涵蓋注解應(yīng)用、方法調(diào)用、異常處理、數(shù)據(jù)庫(kù)支持、傳播行為、類管理與事務(wù)管理器配置等,強(qiáng)調(diào)正確配置對(duì)數(shù)據(jù)一致性的重要性2025-06-06

