? extends T的核心作用是确保读取元素可安全当作T类型使用:get()返回值可直接赋给T或其父类,遍历时可用for(T t: list),可调用T定义的方法;但不可用具体子类型接收,因编译器无法确定实际子类型。

Java 泛型通配符的上限(? extends T)在读取时的核心作用是:**让编译器能保证取出的每个元素至少是 T 类型,从而安全调用 T 及其父类定义的方法**。它不关心具体是什么子类型,只确保“读出来的东西能当 T 用”。
读取时能做什么?
当你声明 List<? extends Number> 这样的集合后:
- 调用
get(i)返回值可直接赋给Number或其任意父类(如Object),无需强转 - 遍历时可用
for (Number n : list),编译通过且类型安全 - 可调用
doubleValue()、intValue()、toString()等Number定义的方法
为什么不能用子类型直接接收?
虽然实际可能是 ArrayList<Integer>,但编译器只知道“它是 Number 的某个子类”,不确定是不是 Double 或 BigDecimal。所以你不能写:
-
Integer i = list.get(0);→ 编译失败(类型不明确) -
Double d = list.get(0);→ 同样失败
必须统一按上界类型(Number)来接收,这是类型安全的底线。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
立即学习“Java免费学习笔记(深入)”;
典型读取场景示例
比如统计数字列表总和:
public static double sum(List<? extends Number> numbers) {
double total = 0.0;
for (Number n : numbers) { // ✅ 安全:n 肯定是 Number 或子类
total += n.doubleValue(); // ✅ 可调用 Number 方法
}
return total;
}
这个方法能接受 List<Integer>、List<Float>、List<BigDecimal> —— 只要它们都“产出 Number”,就符合“Producer”的角色。
注意边界情况
某些上界本身是 final 类(如 ? extends String),虽无子类,但语法仍有效;此时读取仍返回 String,只是无法写入(规则不变)。关键不是“有没有子类”,而是“读出来的能不能当 T 用”。

















