Java嵌套generics类型
为什么必须使用generics类型Map<?, ? extends List>
对于以下test()
方法Map<?, List>
Map<?, ? extends List>
而不是更简单的Map<?, List>
?
public static void main(String[] args) { Map<Integer, List> mappy = new HashMap<Integer, List>(); test(mappy); } public static void test(Map<?, ? extends List> m) {} // Doesn't compile // public static void test(Map<?, List> m) {}
注意到以下工作,并且三种方法无论如何都具有相同的擦除类型。
public static void test(Map<?, List> m) {}
从根本上说, List
和>
List extends List>>
List extends List>>
具有不同的类型参数。
事实上,一个是另一个的子类型,但首先让我们更多地了解它们的含义。
理解语义差异
一般来说,通配符?
代表一些“缺失的信息”。 这意味着“这里有一种类型的论证,但我们不知道它是什么” 。 而且因为我们不知道它是什么,所以对如何使用引用该特定类型参数的任何东西施加限制。
暂时,让我们使用List
而不是Map
来简化示例。
-
List
包含任何类型参数的List 。 所以ie:- >
List
- > theAnyList = new ArrayList
- >(); // we can do this theAnyList.add( new ArrayList
() ); theAnyList.add( new LinkedList () ); List> typeInfoLost = theAnyList.get(0); // but we are prevented from doing this typeInfoLost.add( new Integer(1) ); 我们可以在
theAnyList
放置任何List
,但是这样做我们就失去了对它们元素的了解 。 -
我们用的时候
? extends
? extends
,List
包含List
一些特定子类型,但我们不知道它是什么 。 所以ie:List extends List
> theNotSureList = new ArrayList >(); // we can still use its elements // because we know they store Float List aFloatList = theNotSureList.get(0); aFloatList.add( new Float(1.0f) ); // but we are prevented from doing this theNotSureList.add( new LinkedList () ); 向
theNotSureList
添加任何内容已不再安全,因为我们不知道其元素的实际类型。 (它最初是List
?还是> List
?我们不知道。)> -
我们可以将这些放在一起并有一个
List extends List>>
List extends List>>
。 我们不知道它里面有什么类型的List
,我们也不知道那些List
的元素类型。 所以ie:List extends List>> theReallyNotSureList; // these are fine theReallyNotSureList = theAnyList; theReallyNotSureList = theNotSureList; // but we are prevented from doing this theReallyNotSureList.add( new Vector
() ); // as well as this theReallyNotSureList.get(0).add( "a String" ); 我们丢失了关于
theReallyNotSureList
信息, 以及里面List
的元素类型。(但您可能会注意到我们可以为其分配任何类型的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
? extends
是必要的
您可能知道“具体”generics类型具有不变性 ,即List
不是List
的子类型,即使class Dog extends Animal
。 相反,通配符是我们如何具有协方差 ,即List
是 List extends Animal>
的子类型List extends Animal>
List extends Animal>
。
// Dog is a subtype of Animal class Animal {} class Dog extends Animal {} // List is a subtype of List extends Animal> List extends Animal> a = new ArrayList (); // all parameterized Lists are subtypes of List> List> b = a;
因此,将这些想法应用于嵌套List
:
-
List
是List>
的子类型,但List
不是- >
List
的子类型。 如前所示,这可以防止我们通过向- >
List
添加错误元素来破坏类型安全性。 -
List
是- >
List extends List>>
的子类型List extends List>>
List extends List>>
,因为有界通配符允许协方差。 那是,? extends
? extends
允许List
是List>
的子类型。 -
List extends List>>
List extends List>>
实际上是一个共享的超类型:List extends List>> ╱ ╲ List
- > List
- >
在审查中
-
Map
仅接受> List
作为值。 -
Map, List>>
接受任何List
作为值。 -
Map
和> Map, List>>
是具有单独语义的不同类型。 - 一个人不能转换为另一个,以防止我们以不安全的方式进行修改。
-
Map, ? extends List>>
Map, ? extends List>>
是一个共享超类型,它施加了安全限制:Map, ? extends List>> ╱ ╲ Map, List>> Map
>
generics方法如何工作
通过在方法上使用类型参数,我们可以断言List
具有一些具体类型。
static void test(Map, List > m) {}
此特定声明要求Map
中的所有 List
都具有相同的元素类型。 我们不知道那种类型实际上是什么,但我们可以以抽象的方式使用它。 这允许我们执行“盲”操作。
例如,这种声明可能对某种积累很有用:
static List test(Map, List > m) { List result = new ArrayList (); for(List value : m.values()) { result.addAll(value); } return result; }
我们不能调用put
,因为我们不知道它的关键类型是什么。 但是,我们可以操纵它的值,因为我们知道它们都是具有相同元素类型的 List
。
只是为了踢
问题没有讨论的另一个选择是为List
提供有界通配符和generics类型:
static void test(Map, ? extends List > m) {}
我们可以用Map
类的东西来调用它。 如果我们只关心E
的类型,这是最宽容的声明。
我们也可以使用bounds来嵌套类型参数:
static > void(Map m) { for(K key : m.keySet()) { L list = m.get(key); for(E element : list) { // ... } } }
这既允许我们传递给它的东西,也允许我们如何操纵m
及其中的所有东西。
也可以看看
- “Javagenerics:什么是PECS?” 对于区别
? extends
? extends
和? super
? super
- JLS 4.10.2。 类和接口类型之间的子类型和JLS 4.5.1。 输入参数化类型的参数 ,以获得此答案的技术细节。