理解Java泛型通配符:List<? extends T>与List<? super T>的深度解析

引言

在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,有效平衡类型约束与代码复用需求。

相关推荐
代码山河5 小时前
Python就业方向全解析:后端、爬虫、数据分析、AI你选哪个?
python·学习·架构·教程·面向对象·项目
在世修行5 小时前
智学文枢:题目流水线
python·多线程·状态机·pyside
学长毕设5 小时前
基于SpringBoot的实验室资产管理系统
java·spring boot·后端
梯度下降者5 小时前
零基础入门Python 爬虫
爬虫·python·数据分析·入门指南·webcrawler
zzu123zsw6 小时前
从臃肿的模板调用到声明式契约:深入 UniHttp 动态代理与多渠道生命周期扩展
java·spring boot·microsoft·unihttp
“AI国潮设计-小江”7 小时前
【AIGC实战】Python+Stable Diffusion打造“英歌舞财神”IP,解锁“马到成功”等爆款情绪营销文案设计
开发语言·人工智能·python·prompt·aigc
源码集结号7 小时前
云诊所门诊系统源码,基于SpringBoot+Vue.js架构,支持多租户设计与Docker容器化部署
java·spring boot·源码·医院信息系统·门诊系统·诊所系统
论文复现现场7 小时前
WRF、FEA 能用 L40S 加速吗?求解器检查、断点恢复与长期运行
java·数据库·算法·wrf·有限元分析·gpu计算·断点续算
啃瓜子的松鼠8 小时前
泛微OAe9-后端二开异步调用
java
benchmark_cc8 小时前
用 Python 拉 A 股全市场实时快照:Notebook 请求、结果校验与易错点
python·数据分析·pandas·量化交易·股票数据·quantdash