我遇到了一个问题(简化):
public void method(List<List<?>> list){...}
调用时,给了我一个编译错误:
method(new ArrayList<List<String>>()); // This line gives the error
在阅读similar thread之后,我明白如果我将方法签名重写为:
public void method(List<? extends List<?>> list){...}
现在,我的问题是,为什么以下工作呢?
public <T> void method(List<List<T>> list){...}
答案 0 :(得分:5)
当您处理多级通配符语法时,确实会出现混淆。让我们理解那些类型在那里的确切含义:
List<List<?>>
是具体参数化类型。它是不同类型List<E>
的异构集合。由于List<?>
代表List
所有实例化的系列,因此您无法将ArrayList<List<String>>
传递给List<List<?>>
。因为,没有什么可以阻止你在方法中向它添加List<Integer>
,并且如果编译器允许,它会在运行时崩溃。
List<? extends List<?>>
是通配符参数化类型。它代表了一系列不同类型的List<E>
。基本上,它可能是List<ArrayList<String>>
,List<LinkedList<Date>>
,依此类推。它可以是从List<?>
扩展的任何类型的列表。因此,将ArrayList<List<String>>
传递给它是安全的,原因是,您不会被允许添加任何内容,只有null
到列表中。向列表中添加任何内容都将是编译时错误。
至于List<List<T>>
,它又是具体参数化类型。由于您现在正在处理泛型方法,因此类型参数将被推断为为其传递的类型。因此,对于ArrayList<List<String>>
,类型T
将被推断为T
。泛型方法处理使用它声明的类型。所以,这里只有一种类型T
。对于任何类型List<List<T>>
,您从List<T>
获得的所有列表肯定都是T
。因此,它是{em>同类集合的List
类型。在方法内部,您无法向List<E>
添加任意List<List<T>>
,因为编译器不知道该类型E
是否与T
兼容。所以,这是安全的调用。
相关:强>
答案 1 :(得分:2)
我想我在Angelika Langer's generics FAQ,“案例研究#3”中找到了答案:
如果方法签名使用多级通配符类型,则泛型方法签名与其通配符版本之间始终存在差异。这是一个例子。假设有一个泛型类型Box,我们需要声明一个带有一个方框列表的方法。
示例(带有类型参数的方法):
public static <T> void print1( List <Box<T>> list) {
for (Box<T> box : list) {
System.out.println(box);
}
}
示例(带通配符的方法):
public static void print2( List <Box<?>> list) {
for (Box<?> box : list) {
System.out.println(box);
}
}
这两种方法都是表现良好的方法,但它们并不相同。通用版本需要相同类型的框的同类列表。通配符版本接受不同类型的盒子的异类列表。当调用两个打印方法时,这变得可见。
答案 2 :(得分:1)
基本原因是List<List<?>>
不是List<List<String>>
的超类。
List<List<?>>
可以包含List<Integer>
和List<String>
。
泛型类型必须完全匹配,否则您可能会收到错误的分配。