java泛型本身是不变的,协变(? extends t)和逆变(? super t)通过通配符实现:前者用于只读(producer extends),后者用于只写(consumer super),均在编译期保障类型安全。

Java 泛型本身是**不变的(invariant)**,也就是说 List<string></string> 和 List<object></object> 完全无关,不能互相赋值。协变与逆变不是泛型类型自身的属性,而是通过**通配符(wildcard)** 显式引入的“安全型变”机制——它不改变泛型类本身的不变性,而是在使用时(比如方法参数、变量声明)放宽类型约束,同时保证类型安全。
协变:用 ? extends T 表示“只读”场景
协变对应生产者(Producer),即你从容器中「取」数据,希望拿到更通用的类型。
-
? extends T表示“某个 T 的子类型”,上限是 T,下限未知(可能是 T 本身,也可能是任意子类) - 可以安全地 get(),返回类型是
T(编译器能保证不会越界) - 但不能 add()(除了
null),因为编译器不知道具体是哪个子类,加进去可能破坏类型一致性 - 典型例子:
List extends Number> nums = new ArrayList<integer>();</integer>✅
你可以调用nums.get(0),结果是Number;但nums.add(3.14)❌ 编译失败
逆变:用 ? super T 表示“只写”场景
逆变对应消费者(Consumer),即你向容器中「放」数据,希望接受更宽泛的输入。
-
? super T表示“某个 T 的父类型”,下限是 T,上限是 Object - 可以安全地 add(T),因为任何
T或其子类对象,都能放进T的父类型容器中 - 但 get() 只能返回
Object(最宽泛的类型),因为编译器无法确定具体是什么父类 - 典型例子:
List super Integer> ints = new ArrayList<number>();</number>✅
你可以ints.add(42)或ints.add(new Long(100))❌(Long 不是 Integer 子类,但 Integer 是 Number 子类,所以 add(Integer) ✅);
而Object o = ints.get(0)✅,但Integer i = ints.get(0)❌ 编译失败
记住 PECS 原则:Producer Extends, Consumer Super
这是理解协变/逆变最实用的口诀:
- 你要 往外拿(produce) 元素 → 用
? extends T(协变)→ 安全读取 - 你要 往里塞(consume) 元素 → 用
? super T(逆变)→ 安全写入 - 如果既要读又要写,就别用通配符,直接用具体类型,比如
List<t></t>
为什么数组是协变但泛型不是?
Java 数组在运行时保留类型信息,所以 String[] 是 Object[] 的子类型(协变),但这也导致了运行时 ArrayStoreException 风险;而泛型为避免此类隐患,默认设计为不变,并把型变控制权交给开发者——通过通配符显式、安全地启用协变或逆变,所有检查都在编译期完成。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











