为什么需要泛型通配符?¶
在Java泛型中,我们常常会遇到需要处理一类集合的情况:比如,一个方法可能需要同时处理List<Integer>、List<Double>、List<Number>等不同类型的集合,但又不想为每个类型单独写一个方法。这时候,泛型通配符就能派上用场了。
通配符用?表示,分为两种:上界通配符(? extends T)和下界通配符(? super T),它们能帮助我们统一处理不同子类型的泛型集合,同时保证类型安全。
一、上界通配符(? extends T)¶
什么是上界通配符?¶
上界通配符表示:集合中的元素类型是T的子类或T本身。
语法:List<? extends T> 或 List<? extends Number>(假设T是Number)。
举个例子¶
假设我们有一个类层次结构:Object是根类,Number继承Object,Integer和Double继承Number。
如果我们想写一个方法,能打印所有Number类型(及其子类)的集合元素,可以这样写:
// 打印List中所有元素
public static void printNumbers(List<? extends Number> list) {
for (Number num : list) { // 元素一定是Number类型,安全
System.out.println(num);
}
}
// 调用方法
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
List<Number> numberList = Arrays.asList(10, 20.5, 30);
printNumbers(intList); // 正确(Integer是Number子类)
printNumbers(doubleList); // 正确(Double是Number子类)
printNumbers(numberList); // 正确(Number是上界)
上界通配符的特点¶
-
只能获取元素,不能添加元素
因为编译器不知道集合具体是哪个子类(如List<Integer>或List<Double>),如果尝试添加元素,类型可能不匹配。
❌ 错误示例:list.add(new Integer(10));
✅ 正确:只能获取元素,返回类型是T(如Number)。 -
返回元素类型安全
元素一定是T或其子类,因此可以用T类型接收(如Number)。
二、下界通配符(? super T)¶
什么是下界通配符?¶
下界通配符表示:集合中的元素类型是T的父类或T本身。
语法:List<? super T> 或 List<? super Number>(假设T是Number)。
举个例子¶
假设我们想写一个方法,能往集合中添加Number类型及其子类(如Integer、Double)的元素,同时处理不同父类的集合(如List<Object>、List<Number>):
// 往集合中添加Number类型及其子类
public static void addNumbers(List<? super Number> list) {
list.add(new Integer(10)); // 正确(Integer是Number子类)
list.add(new Double(20.5)); // 正确(Double是Number子类)
list.add(new Number(30)); // 正确(Number是上界)
}
// 调用方法
List<Object> objList = new ArrayList<>();
List<Number> numList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
addNumbers(objList); // 正确(Object是Number的父类)
addNumbers(numList); // 正确(Number是Number的父类)
addNumbers(intList); // 正确(Integer是Number的子类,可作为父类集合处理)
下界通配符的特点¶
-
只能添加元素,不能获取具体类型元素
因为集合可能是Object、Number等父类集合,元素类型不确定,只能返回Object。
❌ 错误示例:Number num = list.get(0);
✅ 正确:只能获取Object类型元素。 -
添加元素类型安全
可以添加T或其子类,编译器能保证类型匹配。
三、关键区别与场景总结¶
| 通配符类型 | 定义 | 核心特点 | 适用场景 |
|---|---|---|---|
? extends T |
元素是T的子类或T本身 | 只能获取元素(返回T),不能添加元素(除null) | 需要读取不同子类集合的元素 |
? super T |
元素是T的父类或T本身 | 只能添加元素(T或其子类),不能获取具体类型元素(只能返回Object) | 需要往不同父类集合添加元素 |
四、避坑指南¶
- 上界通配符不能写“T”,下界通配符不能写“T”
通配符是“不确定类型”的占位符,而T是类型参数(需明确指定类型)。例如:
// 错误写法(上界通配符不能用T作为返回类型)
public static <T extends Number> T getFirst(List<T> list) { ... }
// 正确写法(用类型参数T)
- 避免过度使用通配符
如果方法只需要List<Integer>,直接使用List<Integer>更明确。通配符用于“未知具体类型”的场景。
五、一句话总结¶
- 上界通配符(
? extends T):处理“所有T的子类”,只读不写。 - 下界通配符(
? super T):处理“所有T的父类”,只写不读具体类型。
掌握这两个规则,就能灵活应对Java泛型中不同子类/父类集合的统一处理问题了!