为何List<? extends Customer>无法添加对象,强转后却可行?
为什么List<? extends Customer>不能添加Customer或子类对象,强制转成List却可以?
首先,咱们得先搞明白List<? extends Customer>这个泛型通配符的本质——它代表的是某个继承自Customer的子类的列表,但编译器不知道具体是哪一个子类的列表。
为什么直接调用add()会报错?
拿你的代码举例:
List<? extends Customer> custs2 = new ArrayList<LocalCustomer>(); custs2.add(new Customer()); // 报错 custs2.add(new LocalCustomer("ABC")); // 报错
虽然你知道custs2实际指向的是ArrayList<LocalCustomer>,但编译器只知道它是“Customer某个子类的列表”——它可能是ArrayList<LocalCustomer>,也可能是ArrayList<VIPCustomer>(假设你有这个子类)。
- 如果允许你添加
new Customer(),那万一custs2实际是ArrayList<VIPCustomer>,把Customer对象放进只能存VIPCustomer的列表里,后续取出元素转成VIPCustomer时就会直接抛出ClassCastException,这完全破坏了泛型的类型安全。 - 哪怕你添加的是
LocalCustomer,编译器也不能保证这个列表就是ArrayList<LocalCustomer>——万一它是ArrayList<VIPCustomer>呢?LocalCustomer和VIPCustomer是平级的子类,互相不能转换,所以编译器干脆一刀切:禁止往List<? extends T>里添加任何非null的元素,从根源上避免类型安全问题。
为什么强制转成List就能运行?
当你写((List<Customer>)custs2)时,相当于你在跟编译器说:“别啰嗦,我知道这个列表实际就是能存Customer类型的,你别管了”。编译器会给你一个unchecked警告,但会放行编译。
在你的例子里,custs2实际是ArrayList<LocalCustomer>,而LocalCustomer是Customer的子类,所以把它当成List<Customer>来用,添加LocalCustomer是完全没问题的——因为LocalCustomer本身就是Customer的子类,符合List<Customer>的元素要求。
但这里要敲个警钟:这种强制转换是不安全的!比如如果你这么写:
((List<Customer>)custs2).add(new Customer()); // 后续取出元素 LocalCustomer lc = custs2.get(0); // 运行时直接抛出ClassCastException
因为你往原本只能存LocalCustomer的列表里塞了一个Customer对象,当你试图把它转成LocalCustomer时,运行时就会报错。所以只有当你100%确定列表的实际类型能容纳你要添加的元素时,才能这么做,否则就是给自己埋坑。
总结一下
List<? extends T>是只读型的泛型(除了可以添加null),它的设计目的是让你安全地读取元素(读取出来的元素都可以向上转型为T),而不是写入元素。- 强制转换绕过了编译器的类型检查,虽然能临时解决问题,但本质上破坏了泛型的类型安全机制,存在运行时异常的风险,非必要情况下千万别这么干。
内容的提问来源于stack exchange,提问作者Kushal
相关产品推荐
相关产品推荐

