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> 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 ? extendsList包含List 一些特定子类型,但我们不知道它是什么 。 所以ie:

     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> List> 。 我们不知道它里面有什么类型的List ,我们也不知道那些 List的元素类型。 所以ie:

     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> // ▲ // └ applies to the "inner" List 

Map工作方式相同,它只有更多的类型参数:

 // ┌ Map K argument // │ ┌ Map V argument // ▼ ▼ Map> // ▲ // └ List E argument 

为什么? extends ? extends是必要的

您可能知道“具体”generics类型具有不变性 ,即List不是List的子类型,即使class Dog extends Animal 。 相反,通配符是我们如何具有协方差 ,即List List的子类型List List

 // Dog is a subtype of Animal class Animal {} class Dog extends Animal {} // List is a subtype of List List a = new ArrayList(); // all parameterized Lists are subtypes of List List b = a; 

因此,将这些想法应用于嵌套List

  • ListList的子类型,但List> 不是 List>的子类型。 如前所示,这可以防止我们通过向List添加错误元素来破坏类型安全性。
  • List> List>的子类型List> List> ,因为有界通配符允许协方差。 那是, ? extends ? extends允许ListList的子类型。
  • List> List>实际上是一个共享的超类型:

      List> ╱ ╲ List> List> 

在审查中

  1. Map> 接受List作为值。
  2. Map>接受任何 List作为值。
  3. Map>Map>是具有单独语义的不同类型。
  4. 一个人不能转换为另一个,以防止我们以不安全的方式进行修改。
  5. Map> Map>是一个共享超类型,它施加了安全限制:

      Map> ╱ ╲ Map> Map> 

generics方法如何工作

通过在方法上使用类型参数,我们可以断言List具有一些具体类型。

 static  void test(Map> m) {} 

此特定声明要求Map中的所有 List都具有相同的元素类型。 我们不知道那种类型实际上什么,但我们可以以抽象的方式使用它。 这允许我们执行“盲”操作。

例如,这种声明可能对某种积累很有用:

 static  List test(Map> m) { List result = new ArrayList(); for(List value : m.values()) { result.addAll(value); } return result; } 

我们不能调用put ,因为我们不知道它的关键类型是什么。 但是,我们可以操纵它的值,因为我们知道它们都是具有相同元素类型的 List

只是为了踢

问题没有讨论的另一个选择是为List提供有界通配符和generics类型:

 static  void test(Map> 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。 输入参数化类型的参数 ,以获得此答案的技术细节。

这是因为generics的子类化规则与您的预期略有不同。 特别是如果你有:

 class A{} class B extends A{} 

然后

List不是List的子类

这里详细解释, 这里解释了通配符(“?”字符)的用法。