Java 嵌套泛型类型

为什么必须使用泛型类型而不是更简单的以下方法?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) {}

答案 1

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

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

了解语义差异

一般来说,通配符表示一些“缺失的信息”。这意味着“这里曾经有过一个类型参数,但我们不知道它是什么了”。因为我们不知道它是什么,所以我们对如何使用引用该特定类型参数的任何内容施加了限制。?

目前,让我们通过使用 代替 来简化示例。ListMap

  • A 包含具有任何类型参数的任何类型的 List。因此,即: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) );
    

    我们可以把任何东西放进去,但是这样做我们已经失去了对它们元素的知识。ListtheAnyList

  • 当我们使用 时,它包含 List 的一些特定子类型,但我们不知道它是什么了。因此,即:? extendsList

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

    向 中添加任何内容都不再安全,因为我们不知道其元素的实际类型。(它最初是一个?还是 ?我们不得而知。theNotSureListList<LinkedList<Float>>List<Vector<Float>>

  • 我们可以把它们放在一起,并有一个.我们不知道它里面有什么类型的元素,我们也不知道这些的元素类型。因此,即:List<? extends List<?>>ListList

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

    我们丢失了有关 的信息以及其中 的 s 的元素类型。theReallyNotSureListList

    (但您可能会注意到,我们可以为其分配任何类型的列表持有列表...)

所以分解一下:

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

您可能知道“具体”泛型类型具有不变性,即 List<Dog> 不是 List<Animal>的子类型,即使 .相反,通配符是我们获得协方差的方式,即 的子类型。class Dog extends AnimalList<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>> 的子类型,因为有界通配符允许协方差。也就是说,允许考虑的子类型的事实。List<? extends List<?>>? extendsList<String>List<?>
  • List<? extends List<?>>实际上是一个共享的超类型:

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

审查中

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

            Map<?, ? extends List<?>>
                 ╱          ╲
    Map<?, List<?>>     Map<Integer, List<String>>
    

泛型方法的工作原理

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

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

此特定声明要求 中的所有 s 都具有相同的元素类型。我们不知道这种类型到底是什么,但我们可以抽象地使用它。这使我们能够执行“盲目”操作。ListMap

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

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;
}

我们无法调用,因为我们不知道它的密钥类型是什么了。但是,我们可以操作其,因为我们了解它们都具有相同的元素类型putmList

仅用于踢球

该问题未讨论的另一个选项是为 :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


另请参见


答案 2

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

class A{}
class B extends A{}

然后

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

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


推荐