? extends T 限制添加是为保障类型安全:底层具体类型未知,add可能破坏一致性;但读取安全,因所有子类型都可向上转型为T;唯一允许add的是null。

用了 ? extends T 之后不能 add,不是编译器“故意为难”,而是为了守住类型安全的底线——它不知道你往里塞的东西,会不会和底层实际类型冲突。
核心原因:底层类型未知,添加即可能越界
声明 List extends Number> list,意思是“这个引用指向某个具体的子类型列表”,比如可能是:
ArrayListArrayListArrayList
但编译器完全不确定是哪一个。所以当你写 list.add(new Integer(42)) 时:
- 如果实际是
ArrayList,加 Integer 就直接破坏类型一致性 - 如果实际是
ArrayList,这一步看似合法,但编译器必须保证“对所有可能的具体类型都成立” - 只要存在一种可能失败的情形,编译器就禁止该操作
为什么读取却安全?
因为所有子类型都继承自上界类型 T:
-
list.get(0)的返回值,一定能赋给Number或其父类(如Object) - 调用
doubleValue()、intValue()等方法不会出错 - 这是向上转型,天然安全
唯一允许的写入:null
null 是所有引用类型的合法值,不携带具体类型信息,不会引发歧义:
-
list.add(null)始终通过编译 - 但它不解决业务需求,只是语法上的例外
对比:不用通配符时为什么可以 add?
比如 List:
- 明确声明容器接受
Number及其任意子类实例 -
add(new Integer(1))和add(new Double(1.0))都符合契约 - 类型边界清晰,无不确定性
正确应对方式:按需选型,不硬套通配符
如果你需要往集合里存多种子类对象,就该用具体上界类型,而不是通配符:
- ✅
List—— 支持读写numbers = new ArrayList(); - ✅
void process(List extends Number> src)—— 方法参数只读场景 - ❌
List extends Number> numbers = new ArrayList();—— 自己创建后还想 add,逻辑矛盾










