Java嵌套泛型类型

29

为什么在下面的test()方法中必须使用通用类型Map<?, ? extends List<?>>而不是更简单的Map<?, List<?>>

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) {}

你所得到的编译错误信息是什么? - Mik378
类型 Main 中的方法 test(Map<?,List<?>>) 不适用于参数 (Map<Integer,List<String>>) - Louis Bliss
不算是一个完整的答案,但你可以使用 Map<?, List<?>> 。你需要将其作为参数传递给 Map<Integer, List<?>> mappy = new HashMap<>(); 方法。 - Balder
我确实可以这样做,但是这样我就失去了对我放入mappy的内容进行类型检查的能力!例如,我可以写mappy.put(123, new ArrayList<Integer>())而不会出错,这是不希望看到的。 - Louis Bliss
2个回答

68

基本上,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<?>>();
    
    // 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) );
    

    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>>();
    
    // 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>() );
    

    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 Lists either. So i.e.:

    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" );
    

    We've lost information both about theReallyNotSureList, as well as the element type of the Lists 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 AnimalList<Dog>不是List<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> is a subtype of List<?> but List<List<String>> is not a subtype of List<List<?>>. As shown before, this prevents us from compromising type safety by adding wrong elements to the List.
  • List<List<String>> is a subtype of List<? extends List<?>>, because the bounded wildcard allows covariance. That is, ? extends allows the fact that List<String> is a subtype of List<?> to be considered.
  • List<? extends List<?>> is in fact a shared supertype:

         List<? extends List<?>>
              ╱          ╲
    List<List<?>>    List<List<String>>
    

审核中

  1. Map<Integer, List<String>> accepts only List<String> as a value.
  2. Map<?, List<?>> accepts any List as a value.
  3. Map<Integer, List<String>> and Map<?, List<?>> are distinct types which have separate semantics.
  4. One cannot be converted to the other, to prevent us from doing modifications in an unsafe way.
  5. 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 及其内部所有内容也非常宽容。


参见


1
我明白List<List<?>>List<? extends List<?>>不同,这一点对我来说很清楚。我的问题更多的是为什么在我的情况下我不能使用前者,而可以使用后者甚至List<List<E>>。编辑:“对于泛型方法,接受Map<?, List<E>>的方法略有不同,但基本上类似。”抱歉? - Louis Bliss
Louis,? extends适用于Map的V。您的Map将参数化List作为V。这意味着它只能在其中存储List<String>。接受List<?>的Map可以接受任何类型的List。 - Radiodef
好的,明白了。 - Louis Bliss
1
您,先生,是个天才。非常感谢您提供如此好的解释! - Maurice Müller

1
这是因为泛型的子类化规则与您预期的略有不同。特别是,如果您有以下情况:
class A{}
class B extends A{}

那么

List<B> 不是 List<A> 的子类。

这在这里有详细解释,通配符("?"字符)的用法在这里有解释。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接