引言
在Java泛型系统中,通配符?
是实现类型安全与灵活性的重要工具。其中List<? extends T>
和List<? super T>
的差异常令开发者困惑。本文将通过理论解析、代码示例和实践场景,揭示它们的核心区别及应用场景。
一、基础概念
1.1 上界通配符(Upper Bounded Wildcard)
List<? extends T>
声明了一个只读容器 ,表示集合元素是T
或其子类:
java
void processNumbers(List<? extends Number> list) {
Number n = list.get(0); // 安全读取
// list.add(1); 编译错误!
}
此列表无法写入(除null
),因实际类型可能是List<Integer>
或List<Double>
。
1.2 下界通配符(Lower Bounded Wildcard)
List<? super T>
创建了可写容器 ,表示集合元素是T
或其父类:
java
void fillIntegers(List<? super Integer> list) {
list.add(42); // 安全写入
Object obj = list.get(0); // 需强制转型
}
允许添加T
类型元素,但读取时仅能获得Object
类型。
二、核心差异对比
特性 | List<? extends T> | List<? super T> |
---|---|---|
类型边界 | 元素类型<=T | 元素类型>=T |
数据流向 | 生产者(数据输出) | 消费者(数据输入) |
读取操作 | 返回具体类型T | 返回Object |
写入操作 | 禁止(除null) | 允许添加T及其子类 |
典型应用场景 | 计算结果/遍历元素 | 收集数据/回调处理 |
三、PECS原则实践
3.1 Producer-Extends
当集合作为数据生产者时使用extends:
java
double sum(List<? extends Number> nums) {
return nums.stream()
.mapToDouble(Number::doubleValue)
.sum();
}
可接受List<Integer>
, List<Double>
等参数。
3.2 Consumer-Super
当集合作为数据消费者时使用super:
java
void pushAll(List<? super Integer> dest, List<Integer> src) {
dest.addAll(src); // 安全注入数据
}
允许目标列表为List<Number>
或List<Object>
。
四、典型应用场景
4.1 集合工具类实现
观察java.util.Collections.copy()
源码:
java
public static <T> void copy(
List<? super T> dest, // 消费者
List<? extends T> src // 生产者
) {
for (int i=0; i<src.size(); i++)
dest.set(i, src.get(i));
}
该设计确保:
-
源集合安全输出元素
-
目标集合安全接收元素
4.2 回调处理模式
java
interface Processor<T> {
void process(List<? super T> results);
}
void execute(Processor<? super String> processor) {
processor.process(new ArrayList<Object>());
}
允许处理器处理更宽泛的结果类型。
五、常见误区解析
5.1 错误写入extends列表
java
List<? extends Number> nums = new ArrayList<Double>();
nums.add(1.0); // 编译错误!
编译器无法确认实际类型是否匹配。
5.2 错误读取super列表
java
List<? super Integer> list = new ArrayList<Number>();
Integer i = list.get(0); // 需要显式转型
Object obj = list.get(0); // 安全方式
六、进阶应用技巧
6.1 多重边界组合
java
<T extends Comparable<? super T>> void sort(List<T> list) {
// 支持父类实现Comparable的情况
}
允许T
或其父类实现比较接口。
6.2 类型推断优化
java
static <T> void copy(
List<? super T> dest,
List<? extends T> src
) {
// 更优的类型推断
}
相比非通配符版本,增强API灵活性。
结语
理解extends
与super
通配符的本质差异,关键在于把握数据流动方向。遵循PECS原则可使代码兼具类型安全与灵活性。建议在以下场景选择:
-
需要只读访问时使用
? extends T
-
需要写入操作时使用
? super T
-
同时需要读写时避免使用通配符
通过合理运用这些特性,开发者可以构建出更健壮、更灵活的泛型API,有效平衡类型约束与代码复用需求。