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

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

三、上界通配符:? extends T(表示 “T 或 T 的子類”)
上界通配符? extends T表示 “未知類型,但其必須是 T 或 T 的子類”。核心場景是:需要讀取元素,且元素類型是 T 的子類型(即 “Producer”—— 生產(chǎn)者,只產(chǎn)出 T 類型的元素)。
1. 上界通配符的用法
當方法需要從集合中讀取元素,且希望元素是某個類型的子類型(如從 “數(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)
和無界通配符類似,? extends T的具體類型未知(可能是 T 的任意子類),編譯器無法確定集合能接收什么類型的元素,因此不能添加非 null 元素。
List<? extends Number> nums = new ArrayList<Integer>(); nums.add(null); // 合法 // nums.add(1); // 編譯報錯:無法確定nums是否能接收Integer(可能是List<Double>) // nums.add(3.14); // 編譯報錯:同理,可能是List<Integer>
3. 為什么上界通配符適合 “讀取”?
因為不管具體類型是 T 的哪個子類,都可以安全地向上轉(zhuǎn)型為 T。例如List<? extends Number>中的元素,無論實際是 Integer 還是 Double,都能被Number類型接收,因此讀取操作是安全的。
上界通配符圖解

四、下界通配符:? super T(表示 “T 或 T 的父類”)
下界通配符? super T表示 “未知類型,但其必須是 T 或 T 的父類”。核心場景是:需要向集合中添加元素,且元素類型是 T 的子類型(即 “Consumer”—— 消費者,只接收 T 類型的元素)。
1. 下界通配符的用法
當方法需要向集合中添加元素,且希望元素是某個類型的子類型(如向 “動物集合” 中添加狗或貓,因為它們都是動物),用? super T。
示例:向集合中添加元素(動物集合)
// 下界通配符:接收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
因為? super T的具體類型是 T 的父類(可能是 T、T 的父類、Object),編譯器無法確定具體是哪個父類,所以讀取元素時只能用 Object 接收。
List<? super Animal> animals = new ArrayList<Object>(); animals.add(new Dog()); // 合法 Object obj = animals.get(0); // 只能用Object接收 // Animal animal = animals.get(0); // 編譯報錯:可能是List<Object>,不能確定是Animal
3. 為什么下界通配符適合 “寫入”?
因為 T 的子類可以安全地向上轉(zhuǎn)型為 T 的父類。例如List<? super Animal>可以接收 Animal 的子類(Dog、Cat),因為它們都能被轉(zhuǎn)型為 Animal(或其父類),因此寫入操作是安全的。
下界通配符圖解

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

六、常見誤區(qū)與避坑指南
通配符不能用于泛型類 / 接口的定義通配符只能用于方法參數(shù)、變量聲明,不能在定義泛型類或接口時使用:
// 錯誤:泛型類定義不能用通配符 class MyClass<?> { ... }List<?>與List<Object>不同List<Object>可以添加任意類型的元素(因為 Object 是所有類的父類);List<?>不能添加非 null 元素(因為類型完全未知)。
上界和下界的嵌套使用復雜場景可能需要嵌套通配符,例如
Map<? extends K, ? super V>,遵循 PECS 原則即可:鍵是生產(chǎn)者(用 extends),值是消費者(用 super)。通配符與泛型方法的選擇
- 若方法需要返回與輸入同類型的元素,用泛型方法(如
public <T> T getFirst(List<T> list)); - 若方法只需要讀取或?qū)懭?,且不關心具體類型,用通配符更簡潔。
- 若方法需要返回與輸入同類型的元素,用泛型方法(如
七、總結(jié)
通配符是 Java 泛型中提升靈活性的關鍵,核心要點:
| 通配符類型 | 語法 | 含義 | 適用場景 | 操作限制 |
|---|---|---|---|---|
| 無界通配符 | ? | 任意類型 | 只讀取,用 Object 方法 | 可添加 null,讀取用 Object |
| 上界通配符 | ? extends T | T 或 T 的子類 | 讀取元素(生產(chǎn)者) | 可添加 null,讀取用 T |
| 下界通配符 | ? super T | T 或 T 的父類 | 寫入元素(消費者) | 可添加 T 及其子類,讀取用 Object |
記住 PECS 原則:“Producer Extends, Consumer Super”,遇到泛型集合操作時,先判斷是 “讀” 還是 “寫”,再選擇對應的通配符。
通配符的設計體現(xiàn)了 Java 泛型 “安全與靈活平衡” 的思想 —— 既打破了嚴格的不變性,又通過編譯時檢查保證類型安全。熟練掌握通配符,能讓你在處理集合、泛型組件時寫出更簡潔、更通用的代碼!
到此這篇關于Java 通配符詳解:?、? extends、? super 一篇搞懂的文章就介紹到這了,更多相關Java 通配符 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java C++ leetcode執(zhí)行一次字符串交換能否使兩個字符串相等
這篇文章主要為大家介紹了Java C++ leetcode1790執(zhí)行一次字符串交換能否使兩個字符串相等,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10
Spring Cloud 2020.0.0正式發(fā)布再見了Netflix
這篇文章主要介紹了Spring Cloud 2020.0.0正式發(fā)布再見了Netflix,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12
JDK動態(tài)代理接口和接口實現(xiàn)類深入詳解
這篇文章主要介紹了JDK動態(tài)代理接口和接口實現(xiàn)類,JDK動態(tài)代理是代理模式的一種實現(xiàn)方式,因為它是基于接口來做代理的,所以也常被稱為接口代理,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-06-06

