java泛型默认不变,但通过? extends t(协变,读取安全)和? super t(逆变,写入安全)实现类型放宽,遵循pecs原则:producer-extends、consumer-super。

Java 泛型本身是不变的,但通配符(? extends T 和 ? super T)提供了安全的类型放宽机制——协变用于读取,逆变用于写入。PECS 原则正是对这两种使用场景的精准概括:Producer-Extends,Consumer-Super。
协变(? extends T)对应“生产者”
当你只从集合中获取元素、不往里放时,用 ? extends T。它表示“能装 T 或其子类”,所以读出来的元素至少是 T 类型,可安全赋值给 T 变量。
- 例如:
List extends Number>可以是List<integer></integer>或List<double></double>,从中get(0)得到的是Number,安全 - 但不能调用
add(...)—— 编译器不知道具体是哪个子类型,加Integer还是Double都可能出错 - 本质是“只出不进”,适合作为数据源(Producer)
逆变(? super T)对应“消费者”
当你只往集合中添加元素、不关心读出来是什么时,用 ? super T。它表示“能装 T 或其父类”,所以往里加 T 是安全的,因为任何 T 都能放进 T 的父类容器中。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 例如:
List super Integer>可以是List<integer></integer>、List<number></number>或List<object></object>,往里add(new Integer(42))总是合法 - 但
get(0)返回的是Object(最宽上界),无法直接当Integer用,除非强转 - 本质是“只进不出”,适合作为数据目标(Consumer)
为什么不能混用?类型安全是硬约束
泛型不变性防止了运行时类型错误。数组允许协变(String[] 赋给 Object[]),但会引发 ArrayStoreException;泛型通过编译期限制彻底规避这类风险。
-
List<string></string>不是List<object></object>的子类型,否则list.add(new Object())就会污染字符串列表 - 通配符不是绕过规则,而是按需启用受限的型变:读就协变,写就逆变,各守边界
- 如果既要读又要写,就别用通配符,直接用确定类型,比如
List<t></t>
一个典型应用:Collections.copy
这个方法签名是:public static <t> void copy(List super T> dest, List extends T> src)</t>
- 目标列表
dest是消费者 →? super T,确保能接收所有T实例 - 源列表
src是生产者 →? extends T,确保能安全取出T或其子类实例 - 两个通配符共同保障了任意兼容类型的列表间复制,又不牺牲类型安全
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










