允许 list

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










