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