从根本上来说,List<List<?>>
and List<? extends List<?>>
有不同的类型参数。
实际上,一种类型是另一种类型的子类型,但首先让我们详细了解它们各自的含义。
理解语义差异
一般来说,通配符?
代表一些“缺失的信息”。它的意思是“这里曾经有一个类型参数,但我们不再知道它是什么了”。而且因为我们不知道它是什么,所以我们对如何使用引用该特定类型参数的任何内容施加了限制。
目前,让我们使用以下方法来简化示例List
代替Map
.
-
A List<List<?>>
holds 具有任何类型参数的任何类型的列表。所以即:
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
in theAnyList
,但这样做我们就失去了关于他们的元素.
-
当我们使用? extends
, the List
holds 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
,因为我们不知道其元素的实际类型。 (Was它原本是一个List<LinkedList<Float>>
? Or a List<Vector<Float>>
?我们不知道。)
-
我们可以把它们放在一起并得到一个List<? extends List<?>>
。我们不知道什么类型List
它已经存在了,而且我们不知道它的元素类型those 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" );
我们丢失了信息both about theReallyNotSureList
, 也的元素类型List
就在里面。
(但您可能会注意到,我们可以assign任何一种持有清单列表对它...)
所以要分解它:
// ┌ applies to the "outer" List
// ▼
List<? extends List<?>>
// ▲
// └ applies to the "inner" List
The Map
工作方式相同,只是有更多类型参数:
// ┌ Map K argument
// │ ┌ Map V argument
// ▼ ▼
Map<?, ? extends List<?>>
// ▲
// └ List E argument
Why ? extends
是必要的
你可能知道“具体的”泛型类型有不变性, 那是,List<Dog>不是 的子类型List<Animal>即使class Dog extends Animal
。相反,通配符就是我们的方式协方差, 那是,List<Dog>
is的一个子类型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>>
接受only List<String>
作为一个值。
-
Map<?, List<?>>
接受any List
作为一个值。
-
Map<Integer, List<String>>
and Map<?, List<?>>
是具有不同语义的不同类型。
- 一个不能转换为另一个,以防止我们以不安全的方式进行修改。
-
Map<?, ? extends List<?>>
是一个施加安全限制的共享超类型:
Map<?, ? extends List<?>>
╱ ╲
Map<?, List<?>> Map<Integer, List<String>>
通用方法如何工作
通过在方法上使用类型参数,我们可以断言List
有一些具体的类型。
static <E> void test(Map<?, List<E>> m) {}
该特定声明要求all List
是在Map
具有相同的元素类型。我们不知道该类型实际上是什么is,但我们可以以抽象的方式使用它。这使得我们可以进行“盲”操作。
例如,这种声明可能对某种累积有用:
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
on m
因为我们不知道它是什么key type不再是了。然而,我们可以操纵它values因为我们知道他们都是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
以及其中的一切。
See also
-
“Java 泛型:什么是 PECS?”对于之间的差异
? extends
and ? super
.
-
JLS 4.10.2。类和接口类型之间的子类型化 and JLS 4.5.1。参数化类型的类型参数获取此答案的技术细节的切入点。