? extends T 表示上界通配符,限定为T或其子类,编译期强制类型安全;只允许读取和赋null,禁止写入具体对象,因实际类型未知;区别于<T extends U>类型参数声明,后者在作用域内具名且可用。

Java 中的上限通配符 ? extends T 表示“某个未知的具体类型,但该类型一定是 T 或其子类”。它不指定具体是什么类型,只划定一个**上界**——编译器只知道它 ≤ T(按继承关系),但不知道它到底是谁。
它怎么限制上界?
限制方式是**静态、编译期强制的类型安全约束**:声明了 ? extends T 的变量或参数,只能被赋予 List<T>、List<SubT1>、List<SubT2> 等具体泛型类型的引用,但不能是 List<Object> 或 List<String>(除非 String 是 T 的子类)。
-
List<? extends Number> list = new ArrayList<Integer>();✅ 合法(Integer 是 Number 子类) -
List<? extends Number> list = new ArrayList<String>();❌ 编译失败(String 不是 Number 子类) -
List<? extends Number> list = new ArrayList<Number>();✅ 合法(T 自身也满足 “T 或其子类”)
为什么只能读、不能写?
因为“未知具体类型”导致写入不安全。假设你有:
List<? extends Shape> shapes = new ArrayList<Circle>();
立即学习“Java免费学习笔记(深入)”;
此时底层实际是 ArrayList<Circle>,但编译器只看到“某个 Shape 子类”,可能是 Circle、Rectangle,甚至还没写的 Triangle。如果你允许 shapes.add(new Rectangle()),就可能把 Rectangle 塞进一个专为 Circle 设计的列表,破坏类型一致性。
-
Shape s = shapes.get(0);✅ 安全:所有子类都能向上转型为 Shape -
shapes.add(new Circle());❌ 编译错误:无法确认 Circle 是否匹配底层真实类型 -
shapes.add(null);✅ 唯一允许的写入:null 可赋给任意引用类型
和 <T extends U> 的区别
二者都用 extends,但用途不同:
-
<T extends Number>是**类型参数声明**,用于泛型类/方法定义,T 在整个作用域内是确定且可使用的具体类型(如能调用t.doubleValue()) -
? extends Number是**通配符**,用于变量、参数、返回值等“一次性”场景,代表一个不可知、不可命名的子类型,仅支持安全读取操作
典型使用场景
遵循 PECS 原则(Producer Extends, Consumer Super):
- 当方法只从集合中 读取 元素并当作上界类型处理时,用
? extends T - 例如:统计数值总和、打印图形信息、序列化任意子类型对象
- 反例:向集合添加元素、构造新实例、做子类型专属操作(如调用
circle.getRadius())——这些需要明确知道具体类型,不能依赖通配符


















