Java泛型疑问:同下界集合调用addAll为何被禁止?
先看你的代码:
List<? super Number> numList = Arrays.asList(1, 2, 3.0, "hello"); List<? super Number> l = new ArrayList<>(); l.add(1); numList.addAll(l); // <----- 报错行
报错信息:
'addAll(java.util.Collection<? extends capture<? super java.lang.Number>>)' in 'java.util.List' cannot be applied to '(java.util.List<capture<? super java.lang.Number>>)'
核心原因:通配符捕获的不确定性
要理解这个错误,得先看List的addAll方法签名:
boolean addAll(Collection<? extends E> c)
这里的E是当前List的泛型参数。当你声明List<? super Number>时,编译器会把这个通配符? super Number看作一个捕获类型(比如临时命名为Capture#1),它代表某个Number的超类型——可能是Number本身,也可能是Object。
addAll要求传入的集合必须是Collection<? extends Capture#1>,意思是这个集合的元素必须是Capture#1的子类型,这样才能安全地把元素添加到当前列表中。
但你的参数l是另一个List<? super Number>,它对应的捕获类型是Capture#2,同样是Number的某个超类型。编译器无法确定Capture#2是不是Capture#1的子类型:
- 比如
numList实际是List<Number>(Capture#1=Number),而l实际是List<Object>(Capture#2=Object),这时候List<Object>里可能包含非Number的元素,把它加到List<Number>里会导致类型错误。 - 反过来,如果
numList是List<Object>、l是List<Number>,这种情况是安全的,但编译器不会为个别安全场景放宽检查,它要保证所有情况都符合类型安全规则。
解决方法
如果你确定两个列表的元素类型兼容,可以通过以下方式修复:
使用具体类型代替通配符:把两个变量都声明为
List<Number>,让编译器明确类型,addAll即可正常调用:List<Number> numList = new ArrayList<>(Arrays.asList(1, 2, 3.0)); List<Number> l = new ArrayList<>(); l.add(1); numList.addAll(l);(注意:原代码中
Arrays.asList(1,2,3.0,"hello")实际是List<Object>,因为包含String,转成List<Number>需要先过滤非Number元素)调整参数的通配符类型:如果
numList必须是List<? super Number>,可以把l声明为List<? extends Number>——Number及其子类必然是? super Number的子类型,编译器会允许这种调用:List<? super Number> numList = new ArrayList<>(); List<? extends Number> l = new ArrayList<>(); l.add(1); numList.addAll(l);
内容的提问来源于stack exchange,提问作者Khanna111

