从根本上说,List<List<?>> 和 List<? extends List<?>> 具有不同的类型参数。
实际上,一个是另一个的子类型,但首先让我们详细了解它们各自的含义。
理解语义差异
一般来说,通配符?代表一些“缺失信息”。这意味着“这里曾经有一个类型参数,但我们不知道它是什么了”。而且因为我们不知道它是什么,所以限制了我们如何使用任何引用该特定类型参数的东西。
目前,让我们使用List 代替Map 来简化示例。
-
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,但这样做我们就失去了它们的元素的知识。
-
当我们使用? extends 时,List 包含 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,我们也不知道那些 Lists 的元素类型。所以即:
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关于theReallyNotSureList的信息,以及其中Lists的元素类型。
(但您可能会注意到,我们可以分配任何类型的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
你可能知道"concrete"泛型类型具有不变性,即List<Dog> is not a subtype of 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<?>> 接受 any 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) {}
此特定声明要求Map 中的所有 Lists 具有相同的元素类型。我们不知道该类型实际上是什么,但我们可以以抽象的方式使用它。这允许我们执行“盲”操作。
例如,这种声明可能对某种积累有用:
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;
}
我们不能在m 上调用put,因为我们不再知道它的键类型是什么。但是,我们可以操纵它的值,因为我们知道它们都是List,具有相同的元素类型。
只是为了好玩
问题未讨论的另一个选项是为List 提供有界通配符和泛型类型:
static <E> void test(Map<?, ? extends List<E>> m) {}
我们可以使用Map<Integer, ArrayList<String>> 之类的名称来调用它。如果我们只关心 E 的类型,这是最宽容的声明。
我们也可以使用bounds来嵌套类型参数:
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 及其中的所有内容。
另见