允许 List<? extends Number> 接收 ArrayList<Integer>,因泛型上限通配符表达安全读取契约:编译器确认所有元素可视为 Number,但禁止写入以维持类型安全,从而提升复用性。

因为这是泛型的**上限通配符(upper bounded wildcard)**的合法用法,它不表示类型继承关系,而是表达一种**安全的读取契约**:编译器允许这种写法,是为了在保持类型安全的前提下,提升代码复用性。
它不是“赋值给父类”,而是“接受子类的容器”
表面上看像是把 List<Integer> 赋给 List<? extends Number>,但其实这不是向上转型(inheritance),而是一种**类型兼容性声明**。Java 规定:
-
List<? extends Number>表示“某个具体类型的 List,该类型是Number或其任意子类” -
ArrayList<Integer>正好满足这个条件(Integer是Number的子类) - 所以编译器认可这个赋值,因为它能静态验证:无论实际是
List<Integer>、List<Double>还是List<Number>,从中读出的元素一定可以当作Number使用
为什么只允许读、不允许写?
关键在于“不确定具体类型”。假设你写:
List<? extends Number> list = new ArrayList<Integer>();
编译器只知道 list 里装的是“某种 Number 子类”,但不知道到底是哪一种——可能是 Integer,也可能是 Double 或 Float。所以:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
读是安全的:取出来的元素一定能转成
Number(比如调n.doubleValue()),因为所有子类都继承自Number -
写是禁止的:不能
add(new Double(1.0)),否则可能破坏原本ArrayList<Integer>的类型约束;也不能add(new Integer(1)),因为编译器无法确认当前容器是否接受Integer(它可能实际指向List<Double>)
它解决了什么实际问题?
避免为每种数值类型重复写方法。例如这个求和方法:
public static double sum(List<? extends Number> list) { ... }
你可以直接传入:
-
sum(Arrays.asList(1, 2, 3))→List<Integer> -
sum(Arrays.asList(1.1, 2.2))→List<Double> -
sum(new ArrayList<Number>())→List<Number>
不用重载三个版本,也不用牺牲类型安全。
对比错误写法,更能看清设计意图
下面这些是**不允许的**,原因正好反衬出 ? extends 的合理性:
-
List<Number> list = new ArrayList<Integer>();❌ —— 泛型是不变的(invariant),List<Integer>不是List<Number>的子类型 -
List<? extends Number> list = new ArrayList<String>();❌ ——String不是Number的子类,违反上限约束
所以允许 List<? extends Number> = new ArrayList<Integer>(),不是妥协,而是精心设计的、带约束的灵活性。

















