上界通配符 ? extends T 的核心作用是支持安全读取,允许方法接收 T 或其任意子类的集合,读出元素可直接作为 T 使用;但禁止添加非 null 元素,仅允许读取操作如 get()、iterator()、stream() 等。

上界通配符 ? extends T 的核心作用,是让一个方法或接口能安全接收“T 本身或其任意子类”的集合,并只允许从中读取元素——所有读出的值都可被当作 T 类型安全使用,而不会发生类型错误。
为什么能安全读取?
因为 Java 编译器知道:容器中每个元素,必定是 T 或它的子类实例。这意味着它们天然支持向上转型到 T(例如 Integer → Number、Cat → Animal)。调用 get()、遍历、流式处理时,返回值可直接赋给 T 类型变量,无需强制转换,也不会丢失语义。
-
List extends Number> nums = new ArrayList<integer>();</integer>→Number n = nums.get(0);✅ 合法 -
List extends Animal> animals = new ArrayList<dog>();</dog>→Animal a = animals.get(0);✅ 合法 - 即使底层是
ArrayList<biginteger></biginteger>,nums.get(0)仍能安全转为Number,因BigInteger extends Number
读取操作的具体表现
所有不修改集合结构、仅提取数据的操作都被允许,且返回类型受保障:
-
get(i)返回T(实际是? extends T,但可赋值给T) -
iterator().next()得到T类型引用 -
stream().map(...)中可安全调用T定义的方法(如doubleValue()、getName()) -
forEach(System.out::println)或传给接收T参数的方法(如void feed(Animal a))
限制写入,本质是守住类型边界
禁止添加任何非 null 元素,不是设计缺陷,而是类型安全的必要代价:
- 编译器只知道“它是某个未知的
T子类型”,比如可能是List<integer></integer>,也可能是List<double></double> - 若允许
add(new Integer(1)),而实际集合是List<double></double>,运行时就会出现类型不匹配 - 唯一例外是
null——它在所有引用类型中都合法,不引入歧义
典型应用模式
它最自然的用武之地是「只读参数」:
- 工具方法:如
double sum(List extends Number> list),接受List<integer></integer>、List<float></float>等任意数值子类列表 - 统一处理器:如
void logAll(List extends Person> people),对 Student、Staff 等子类列表一视同仁 - 避免类型擦除带来的兼容问题:绕过泛型不变性限制,实现逻辑上的协变读取
不复杂但容易忽略:它不是为了让你“多塞点类型进来”,而是为了让你“放心取、安心用”。只要记住“读得稳、写不得”,就抓住了关键。

















