基本上,List<List<?>>
和 List<? extends List<?>>
有明显的类型参数区别。
事实上,其中一个是另一个的子类型,但首先让我们更深入地了解它们各自的含义。
理解语义差异
一般来说,通配符?
表示某些“缺失信息”。它意味着“曾经这里有一个类型参数,但我们不再知道它是什么了”。因为我们不知道它是什么,所以对于任何引用该特定类型参数的东西,都会施加限制。
暂时,让我们将示例简化为使用List
而不是Map
。
A List<List<?>>
holds any kind of List with any type argument. So i.e.:
List<List<?>> theAnyList = new ArrayList<List<?>>();
theAnyList.add( new ArrayList<String>() );
theAnyList.add( new LinkedList<Integer>() );
List<?> typeInfoLost = theAnyList.get(0);
typeInfoLost.add( new Integer(1) );
We can put any List
in theAnyList
, but by doing so we have lost knowledge of their elements.
When we use ? extends
, the List
holds some specific subtype of List, but we don't know what it is anymore. So i.e.:
List<? extends List<Float>> theNotSureList =
new ArrayList<ArrayList<Float>>();
List<Float> aFloatList = theNotSureList.get(0);
aFloatList.add( new Float(1.0f) );
theNotSureList.add( new LinkedList<Float>() );
It's no longer safe to add anything to the theNotSureList
, because we don't know the actual type of its elements. (Was it originally a List<LinkedList<Float>>
? Or a List<Vector<Float>>
? We don't know.)
We can put these together and have a List<? extends List<?>>
. We don't know what type of List
it has in it anymore, and we don't know the element type of those List
s either. So i.e.:
List<? extends List<?>> theReallyNotSureList;
theReallyNotSureList = theAnyList;
theReallyNotSureList = theNotSureList;
theReallyNotSureList.add( new Vector<Float>() );
theReallyNotSureList.get(0).add( "a String" );
We've lost information both about theReallyNotSureList
, as well as the element type of the List
s inside it.
(But you may note that we can assign any kind of List holding Lists to it...)
因此,简单来说:
// ┌ 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
你可能知道"具体的"泛型类型具有不变性,即使class Dog extends Animal
,List<Dog>
也不是List<Animal>
的子类型。相反,通配符是如何实现协变性,也就是说,List<Dog>
是List<? extends Animal>
的子类型。
class Animal {}
class Dog extends Animal {}
List<? extends Animal> a = new ArrayList<Dog>();
List<?> b = a;
因此,将这些思想应用于嵌套的List
:
审核中
Map<Integer, List<String>>
accepts only List<String>
as a value.
Map<?, List<?>>
accepts any List
as a value.
Map<Integer, List<String>>
and Map<?, List<?>>
are distinct types which have separate semantics.
- One cannot be converted to the other, to prevent us from doing modifications in an unsafe way.
Map<?, ? extends List<?>>
is a shared supertype which imposes safe restrictions:
Map<?, ? extends List<?>>
╱ ╲
Map<?, List<?>> Map<Integer, List<String>>
通用方法的工作原理
通过在方法中使用类型参数,我们可以断言List
具有某种具体类型。
static <E> void test(Map<?, List<E>> m) {}
这个特定的声明要求在Map中所有的List都有相同的元素类型。我们不知道那个类型实际上是什么,但我们可以以抽象方式使用它。这使我们能够执行“盲目”操作。
例如,这种声明可能对某些累加器很有用:
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
的类型,那么这是最宽松的声明。
我们还可以使用限制条件来嵌套类型参数:
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
及其内部所有内容也非常宽容。
参见
Main
中的方法test(Map<?,List<?>>)
不适用于参数(Map<Integer,List<String>>)
。 - Louis BlissMap<?, List<?>>
。你需要将其作为参数传递给Map<Integer, List<?>> mappy = new HashMap<>();
方法。 - Baldermappy.put(123, new ArrayList<Integer>())
而不会出错,这是不希望看到的。 - Louis Bliss