Java嵌套泛型类型

时间:2014-04-02 08:54:08

标签: java generics bounded-wildcard unbounded-wildcard

对于以下Map<?, ? extends List<?>>方法,为什么必须使用泛型类型Map<?, List<?>>而不是更简单的test()

public static void main(String[] args) {
    Map<Integer, List<String>> mappy =
        new HashMap<Integer, List<String>>();

    test(mappy);
}

public static void test(Map<?, ? extends List<?>> m) {}

// Doesn't compile
// public static void test(Map<?, List<?>> m) {}

注意以下方法有效,并且三种方法无论如何都具有相同的擦除类型。

public static <E> void test(Map<?, List<E>> m) {}

2 个答案:

答案 0 :(得分:49)

从根本上说,List<List<?>>List<? extends List<?>>具有不同的类型参数。

事实上,一个是另一个的子类型,但首先让我们更多地了解它们的含义。

理解语义差异

一般来说,通配符?代表了一些&#34;缺少的信息&#34;。这意味着&#34;这里有一个类型参数,但我们不知道它是什么&#34; 。而且因为我们不知道它是什么,所以对我们如何使用引用该特定类型参数的任何东西施加限制。

目前,让我们使用List代替Map来简化示例。

  • 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的类型,我们也不知道那些 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" );
    

    我们已经丢失了关于theReallyNotSureList以及其中List的元素类型 的信息。< / p>

    (但您可能会注意到我们可以指定任何类型的列表保存列表 ...)

所以要打破它:

//   ┌ 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

  • List<String>List<?>的子类型,但List<List<String>> 不是 List<List<?>>的子类型。如前所示,这可以防止我们通过向List添加错误元素来危及类型安全。
  • List<List<String>> 是<{1}}的子类型,因为有界通配符允许协方差。也就是说,List<? extends List<?>>允许考虑? extendsList<String>的子类型。
  • List<?>实际上是一个共享的超类型:

    List<? extends List<?>>

在审核中

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

    Map<?, ? extends List<?>>

  6. 通用方法如何工作

    通过在方法上使用类型参数,我们可以断言 Map<?, ? extends List<?>> ╱ ╲ Map<?, List<?>> Map<Integer, List<String>> 具有某种具体类型。

    List

    此特定声明要求static <E> void test(Map<?, List<E>> m) {} 中的所有 List具有相同的元素类型。我们不知道 的类型是什么,但我们可以抽象地使用它。这使我们能够执行“盲目的”#34;操作

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

    Map

    我们无法在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,因为我们不知道密钥类型已经是什么了。但是,我们可以操纵其,因为我们知道它们都是m,具有相同的元素类型

    只是为了踢

    问题没有讨论的另一个选择是同时拥有有界通配符和List的泛型类型:

    List

    我们可以用static <E> void test(Map<?, ? extends List<E>> m) {} 之类的东西来调用它。如果我们只关心Map<Integer, ArrayList<String>>的类型,那么这是最宽松的声明。

    我们也可以使用bounds来嵌套类型参数:

    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) { // ... } } } 及其中的所有内容。


    另见

答案 1 :(得分:1)

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

class A{}
class B extends A{}

然后

List<B>不是List<A>

的子类

详细解释here并解释了通配符(“?”字符)的用法here