从根本上说,并且具有不同的类型参数。List<List<?>>
List<? extends List<?>>
实际上,一个是另一个的子类型,但首先让我们更多地了解它们各自的含义。
了解语义差异
一般来说,通配符表示一些“缺失的信息”。这意味着“这里曾经有过一个类型参数,但我们不知道它是什么了”。因为我们不知道它是什么,所以我们对如何使用引用该特定类型参数的任何内容施加了限制。?
目前,让我们通过使用 代替 来简化示例。List
Map
-
A 包含具有任何类型参数的任何类型的 List。因此,即:List<List<?>>
List<List<?>> theAnyList = new ArrayList<List<?>>();
// we can do this
theAnyList.add( new ArrayList<String>() );
theAnyList.add( new LinkedList<Integer>() );
List<?> typeInfoLost = theAnyList.get(0);
// but we are prevented from doing this
typeInfoLost.add( new Integer(1) );
我们可以把任何东西放进去,但是这样做我们已经失去了对它们元素的知识。List
theAnyList
-
当我们使用 时,它包含 List 的一些特定子类型,但我们不知道它是什么了。因此,即:? extends
List
List<? extends List<Float>> theNotSureList =
new ArrayList<ArrayList<Float>>();
// we can still use its elements
// because we know they store Float
List<Float> aFloatList = theNotSureList.get(0);
aFloatList.add( new Float(1.0f) );
// but we are prevented from doing this
theNotSureList.add( new LinkedList<Float>() );
向 中添加任何内容都不再安全,因为我们不知道其元素的实际类型。(它最初是一个?还是 ?我们不得而知。theNotSureList
List<LinkedList<Float>>
List<Vector<Float>>
-
我们可以把它们放在一起,并有一个.我们不知道它里面有什么类型的元素,我们也不知道这些的元素类型。因此,即:List<? extends List<?>>
List
List
List<? extends List<?>> theReallyNotSureList;
// these are fine
theReallyNotSureList = theAnyList;
theReallyNotSureList = theNotSureList;
// but we are prevented from doing this
theReallyNotSureList.add( new Vector<Float>() );
// as well as this
theReallyNotSureList.get(0).add( "a String" );
我们丢失了有关 的信息,以及其中 的 s 的元素类型。theReallyNotSureList
List
(但您可能会注意到,我们可以为其分配任何类型的列表持有列表...)
所以分解一下:
// ┌ applies to the "outer" List
// ▼
List<? extends List<?>>
// ▲
// └ applies to the "inner" List
工作方式相同,它只是有更多的类型参数:Map
// ┌ Map K argument
// │ ┌ Map V argument
// ▼ ▼
Map<?, ? extends List<?>>
// ▲
// └ List E argument
为什么有必要? extends
您可能知道“具体”泛型类型具有不变性,即 List<Dog>
不是 List<Animal>
的子类型,即使 .相反,通配符是我们获得协方差的方式,即 是 的子类型。class Dog extends Animal
List<Dog>
List<? extends Animal>
// Dog is a subtype of Animal
class Animal {}
class Dog extends Animal {}
// List<Dog> is a subtype of List<? extends Animal>
List<? extends Animal> a = new ArrayList<Dog>();
// all parameterized Lists are subtypes of List<?>
List<?> b = a;
因此,将这些想法应用于嵌套:List
审查中
-
Map<Integer, List<String>>
仅接受为值。List<String>
-
Map<?, List<?>>
接受任何作为值。List
-
Map<Integer, List<String>>
并且是具有单独语义的不同类型。Map<?, List<?>>
- 一个不能转换为另一个,以防止我们以不安全的方式进行修改。
-
Map<?, ? extends List<?>>
是施加安全限制的共享超类型:
Map<?, ? extends List<?>>
╱ ╲
Map<?, List<?>> Map<Integer, List<String>>
泛型方法的工作原理
通过在方法上使用类型参数,我们可以断言它具有一些具体的类型。List
static <E> void test(Map<?, List<E>> m) {}
此特定声明要求 中的所有 s 都具有相同的元素类型。我们不知道这种类型到底是什么,但我们可以抽象地使用它。这使我们能够执行“盲目”操作。List
Map
例如,这种声明可能对某种积累有用:
static <E> List<E> test(Map<?, List<E>> m) {
List<E> result = new ArrayList<E>();
for(List<E> value : m.values()) {
result.addAll(value);
}
return result;
}
我们无法调用,因为我们不知道它的密钥类型是什么了。但是,我们可以操作其值,因为我们了解它们都具有相同的元素类型。put
m
List
仅用于踢球
该问题未讨论的另一个选项是为 :List
static <E> void test(Map<?, ? extends List<E>> m) {}
我们可以用类似.这是我们最宽松的声明,如果我们只关心的类型。Map<Integer, ArrayList<String>>
E
我们还可以使用边界来嵌套类型参数:
static <K, E, L extends List<E>> void(Map<K, L> m) {
for(K key : m.keySet()) {
L list = m.get(key);
for(E element : list) {
// ...
}
}
}
这既是对我们可以传递给它的东西的宽容,也是对我们如何操纵和其中的一切的宽容。m
另请参见