Java 通配符詳解:?、? extends、? super 一篇搞懂
在 Java 泛型中,通配符(Wildcard)是解決 “泛型類型不確定” 問(wèn)題的關(guān)鍵。你可能見(jiàn)過(guò)List<?>、List<? extends Number>這樣的寫(xiě)法,它們就是通配符的典型應(yīng)用。通配符看似簡(jiǎn)單,卻藏著不少細(xì)節(jié):什么時(shí)候用??? extends和? super有啥區(qū)別?為什么有的場(chǎng)景能存元素,有的只能讀?今天我們就從泛型的 “不變性” 講起,徹底搞懂無(wú)界通配符、上界通配符和下界通配符的用法、場(chǎng)景和底層邏輯,配上直觀圖解,讓你一次掌握!
一、為什么需要通配符?—— 從泛型的 “不變性” 說(shuō)起
泛型有個(gè)重要特性:不變性(Invariance)。簡(jiǎn)單說(shuō)就是:如果B是A的子類,List<B>不是List<A>的子類。這個(gè)特性會(huì)導(dǎo)致一些反直覺(jué)的問(wèn)題,而通配符正是為了解決這些問(wèn)題而生。
泛型不變性的 “坑”
假設(shè)我們有類繼承關(guān)系:Dog extends Animal,現(xiàn)在看下面的代碼:
List<Dog> dogs = new ArrayList<>(); // 編譯報(bào)錯(cuò):List<Dog>不能賦值給List<Animal> List<Animal> animals = dogs;
為什么會(huì)報(bào)錯(cuò)?因?yàn)槿绻试S這種賦值,會(huì)導(dǎo)致類型安全問(wèn)題:
// 假設(shè)上面的賦值合法 animals.add(new Cat()); // 往本應(yīng)存Dog的列表里加了Cat Dog dog = dogs.get(0); // 運(yùn)行時(shí)會(huì)拋ClassCastException(Cat不能轉(zhuǎn)Dog)
泛型的不變性正是為了避免這種安全問(wèn)題 ——編譯時(shí)就阻斷錯(cuò)誤,而不是等到運(yùn)行時(shí)。但這也帶來(lái)了新問(wèn)題:如何編寫(xiě)一個(gè)能處理 “任意 Animal 子類集合” 的通用方法?比如一個(gè)打印所有動(dòng)物的方法,既想接收List<Dog>,也想接收List<Cat>。這時(shí),通配符就派上用場(chǎng)了。
通配符的核心作用
通配符(?)表示 “未知類型”,它的出現(xiàn)打破了泛型的嚴(yán)格不變性,允許在一定范圍內(nèi) “靈活匹配” 泛型類型,同時(shí)又不破壞類型安全。
泛型不變性與通配符作用圖解

二、無(wú)界通配符:?(表示 “任意類型”)
無(wú)界通配符?表示 “未知的任意類型”,可以理解為? extends Object的簡(jiǎn)寫(xiě)。它的核心場(chǎng)景是:只需要使用 Object 類的通用方法,不需要關(guān)注具體類型。
1. 無(wú)界通配符的用法
當(dāng)方法參數(shù)需要接收 “任意泛型類型”,且方法內(nèi)部只讀取元素(不修改),或只調(diào)用Object類的方法(如toString()、equals())時(shí),用?。
示例:打印任意集合的元素
// 無(wú)界通配符:可以接收List<String>、List<Integer>、List<Dog>等任意List
public static void printList(List<?> list) {
for (Object obj : list) { // 只能用Object接收元素
System.out.println(obj); // 調(diào)用Object的toString()
}
}
// 調(diào)用示例
List<String> strList = Arrays.asList("a", "b");
List<Integer> intList = Arrays.asList(1, 2);
printList(strList); // 合法
printList(intList); // 合法
2. 無(wú)界通配符的限制:不能添加元素(除了 null)
因?yàn)?code>?表示 “未知類型”,編譯器無(wú)法確定集合能接收什么類型的元素,所以不能添加非 null 元素(null 是所有類型的默認(rèn)值,例外)。
List<?> list = new ArrayList<String>();
list.add(null); // 合法(null是任意類型的實(shí)例)
// list.add("hello"); // 編譯報(bào)錯(cuò):無(wú)法確定list是否能接收String
3. 無(wú)界通配符 vs 泛型方法
可能有人會(huì)問(wèn):用泛型方法public static <T> void printList(List<T> list)不也能實(shí)現(xiàn)同樣的功能嗎?
兩者的區(qū)別在于:
- 泛型方法的
T是 “確定的未知類型”(調(diào)用時(shí)編譯器會(huì)推斷具體類型),適合需要在方法內(nèi)部使用類型一致的場(chǎng)景(如返回T類型結(jié)果); - 無(wú)界通配符
?是 “完全未知的類型”,適合只需要Object方法的場(chǎng)景,代碼更簡(jiǎn)潔。
無(wú)界通配符圖解

三、上界通配符:? extends T(表示 “T 或 T 的子類”)
上界通配符? extends T表示 “未知類型,但其必須是 T 或 T 的子類”。核心場(chǎng)景是:需要讀取元素,且元素類型是 T 的子類型(即 “Producer”—— 生產(chǎn)者,只產(chǎn)出 T 類型的元素)。
1. 上界通配符的用法
當(dāng)方法需要從集合中讀取元素,且希望元素是某個(gè)類型的子類型(如從 “數(shù)字集合” 中讀取數(shù)字,不管是 Integer 還是 Double),用? extends T。
示例:獲取集合中的最大值(數(shù)字集合)
// 上界通配符:接收Number或其子類(Integer、Double等)的集合
public static double getMax(List<? extends Number> numbers) {
double max = Double.MIN_VALUE;
for (Number num : numbers) { // 安全讀取為Number類型
if (num.doubleValue() > max) {
max = num.doubleValue();
}
}
return max;
}
// 調(diào)用示例
List<Integer> ints = Arrays.asList(1, 3, 2);
List<Double> doubles = Arrays.asList(1.5, 3.8, 2.2);
System.out.println(getMax(ints)); // 3.0
System.out.println(getMax(doubles));// 3.8
2. 上界通配符的限制:不能添加元素(除了 null)
和無(wú)界通配符類似,? extends T的具體類型未知(可能是 T 的任意子類),編譯器無(wú)法確定集合能接收什么類型的元素,因此不能添加非 null 元素。
List<? extends Number> nums = new ArrayList<Integer>(); nums.add(null); // 合法 // nums.add(1); // 編譯報(bào)錯(cuò):無(wú)法確定nums是否能接收Integer(可能是List<Double>) // nums.add(3.14); // 編譯報(bào)錯(cuò):同理,可能是List<Integer>
3. 為什么上界通配符適合 “讀取”?
因?yàn)椴还芫唧w類型是 T 的哪個(gè)子類,都可以安全地向上轉(zhuǎn)型為 T。例如List<? extends Number>中的元素,無(wú)論實(shí)際是 Integer 還是 Double,都能被Number類型接收,因此讀取操作是安全的。
上界通配符圖解

四、下界通配符:? super T(表示 “T 或 T 的父類”)
下界通配符? super T表示 “未知類型,但其必須是 T 或 T 的父類”。核心場(chǎng)景是:需要向集合中添加元素,且元素類型是 T 的子類型(即 “Consumer”—— 消費(fèi)者,只接收 T 類型的元素)。
1. 下界通配符的用法
當(dāng)方法需要向集合中添加元素,且希望元素是某個(gè)類型的子類型(如向 “動(dòng)物集合” 中添加狗或貓,因?yàn)樗鼈兌际莿?dòng)物),用? super T。
示例:向集合中添加元素(動(dòng)物集合)
// 下界通配符:接收Animal或其父類(如Object)的集合
public static void addDogs(List<? super Animal> animals) {
animals.add(new Dog()); // 安全添加Dog(Animal的子類)
animals.add(new Cat()); // 安全添加Cat(Animal的子類)
}
// 調(diào)用示例
List<Animal> animalList = new ArrayList<>();
List<Object> objectList = new ArrayList<>();
addDogs(animalList); // 合法
addDogs(objectList); // 合法
2. 下界通配符的限制:讀取元素只能作為 Object
因?yàn)?code>? super T的具體類型是 T 的父類(可能是 T、T 的父類、Object),編譯器無(wú)法確定具體是哪個(gè)父類,所以讀取元素時(shí)只能用 Object 接收。
List<? super Animal> animals = new ArrayList<Object>(); animals.add(new Dog()); // 合法 Object obj = animals.get(0); // 只能用Object接收 // Animal animal = animals.get(0); // 編譯報(bào)錯(cuò):可能是List<Object>,不能確定是Animal
3. 為什么下界通配符適合 “寫(xiě)入”?
因?yàn)?T 的子類可以安全地向上轉(zhuǎn)型為 T 的父類。例如List<? super Animal>可以接收 Animal 的子類(Dog、Cat),因?yàn)樗鼈兌寄鼙晦D(zhuǎn)型為 Animal(或其父類),因此寫(xiě)入操作是安全的。
下界通配符圖解

五、通配符的經(jīng)典原則:PECS
面對(duì)三種通配符,很多人會(huì)混淆什么時(shí)候用哪個(gè)。記住這個(gè)經(jīng)典原則:PECS(Producer Extends, Consumer Super)—— 生產(chǎn)者用 extends,消費(fèi)者用 super。
Producer(生產(chǎn)者):如果你需要從集合中讀取元素(集合產(chǎn)出元素給你),用
? extends T。例:從 “數(shù)字集合” 中讀取數(shù)字計(jì)算總和,集合是生產(chǎn)者。Consumer(消費(fèi)者):如果你需要向集合中寫(xiě)入元素(你給集合傳入元素),用
? super T。例:向 “動(dòng)物集合” 中添加狗,集合是消費(fèi)者。既讀又寫(xiě):不要用通配符,直接用具體的泛型類型(如
List<T>)。
PECS 原則圖解

六、常見(jiàn)誤區(qū)與避坑指南
通配符不能用于泛型類 / 接口的定義通配符只能用于方法參數(shù)、變量聲明,不能在定義泛型類或接口時(shí)使用:
// 錯(cuò)誤:泛型類定義不能用通配符 class MyClass<?> { ... }List<?>與List<Object>不同List<Object>可以添加任意類型的元素(因?yàn)?Object 是所有類的父類);List<?>不能添加非 null 元素(因?yàn)轭愋屯耆粗?/li>
上界和下界的嵌套使用復(fù)雜場(chǎng)景可能需要嵌套通配符,例如
Map<? extends K, ? super V>,遵循 PECS 原則即可:鍵是生產(chǎn)者(用 extends),值是消費(fèi)者(用 super)。通配符與泛型方法的選擇
- 若方法需要返回與輸入同類型的元素,用泛型方法(如
public <T> T getFirst(List<T> list)); - 若方法只需要讀取或?qū)懭耄也魂P(guān)心具體類型,用通配符更簡(jiǎn)潔。
- 若方法需要返回與輸入同類型的元素,用泛型方法(如
七、總結(jié)
通配符是 Java 泛型中提升靈活性的關(guān)鍵,核心要點(diǎn):
| 通配符類型 | 語(yǔ)法 | 含義 | 適用場(chǎng)景 | 操作限制 |
|---|---|---|---|---|
| 無(wú)界通配符 | ? | 任意類型 | 只讀取,用 Object 方法 | 可添加 null,讀取用 Object |
| 上界通配符 | ? extends T | T 或 T 的子類 | 讀取元素(生產(chǎn)者) | 可添加 null,讀取用 T |
| 下界通配符 | ? super T | T 或 T 的父類 | 寫(xiě)入元素(消費(fèi)者) | 可添加 T 及其子類,讀取用 Object |
記住 PECS 原則:“Producer Extends, Consumer Super”,遇到泛型集合操作時(shí),先判斷是 “讀” 還是 “寫(xiě)”,再選擇對(duì)應(yīng)的通配符。
通配符的設(shè)計(jì)體現(xiàn)了 Java 泛型 “安全與靈活平衡” 的思想 —— 既打破了嚴(yán)格的不變性,又通過(guò)編譯時(shí)檢查保證類型安全。熟練掌握通配符,能讓你在處理集合、泛型組件時(shí)寫(xiě)出更簡(jiǎn)潔、更通用的代碼!
到此這篇關(guān)于Java 通配符詳解:?、? extends、? super 一篇搞懂的文章就介紹到這了,更多相關(guān)Java 通配符 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Spring 兩種注入的方式(Set和構(gòu)造)實(shí)例
本篇文章主要介紹了Spring 兩種注入的方式(Set和構(gòu)造)實(shí)例,Spring框架主要提供了Set注入和構(gòu)造注入兩種依賴注入方式。有興趣的可以了解一下。2017-02-02
SpringBoot+MinIO實(shí)現(xiàn)對(duì)象存儲(chǔ)的示例詳解
MinIO?是一個(gè)基于Apache?License?v2.0開(kāi)源協(xié)議的對(duì)象存儲(chǔ)服務(wù),它是一個(gè)非常輕量的服務(wù),可以很簡(jiǎn)單的和其他應(yīng)用的結(jié)合,所以下面我們就來(lái)看看SpringBoot如何整合MinIO實(shí)現(xiàn)對(duì)象存儲(chǔ)吧2023-10-10
在 Spring Boot 中使用異步線程時(shí)的 HttpServletReque
文章討論了在SpringBoot中使用異步線程時(shí),由于HttpServletRequest復(fù)用導(dǎo)致的Cookie解析失敗問(wèn)題,為了解決這個(gè)問(wèn)題,文章推薦了使用HttpServletRequestWrapper創(chuàng)建請(qǐng)求副本、手動(dòng)傳遞請(qǐng)求上下文和延遲請(qǐng)求清理等方法,感興趣的朋友一起看看吧2025-03-03
JDK21中Sequenced Collections(序列集合)的實(shí)現(xiàn)
Sequenced Collections是Java中的一個(gè)新特性,它提供了一種有序的集合實(shí)現(xiàn),本文主要介紹了JDK21中Sequenced Collections(序列集合)的實(shí)現(xiàn),感興趣的可以了解一下2025-09-09
Java中一個(gè)線程執(zhí)行死循環(huán)有什么后果
這篇文章主要為大家詳細(xì)介紹了Java中一個(gè)線程執(zhí)行死循環(huán)有什么后果,當(dāng)一個(gè)線程在執(zhí)行死循環(huán)時(shí)會(huì)影響另外一個(gè)線程嗎,下面為大家揭曉2016-05-05
java密鑰交換算法DH定義與應(yīng)用實(shí)例分析
這篇文章主要介紹了java密鑰交換算法DH定義與應(yīng)用,結(jié)合實(shí)例形式分析了Java密鑰交換算法DH的原理、定義、使用方法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2019-09-09

