繁体   English   中英

Java嵌套泛型类型

[英]Java nested generic type

为什么必须使用泛型类型Map<?, ? extends List<?>> 对于以下test()方法Map<?, List<?>> 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) {}

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

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

理解语义差异

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

目前,让我们使用List而不是Map来简化示例。

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

    我们可以将任何List放入theAnyList ,但是这样做我们已经失去了对它们元素的了解

  • 当我们使用? extends ? extendsList包含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<? 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的元素类型。

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

所以要分解它:

//   ┌ 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是必要的

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

我们不能调用put on m因为我们不知道它的键类型是什么了。 但是,我们可以操作它的值,因为我们知道它们都是具有相同元素类型的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及其中的所有内容。


也可以看看

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

class A{}
class B extends A{}

然后

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

它的详细解释在这里和通配符(以下简称“?”字符)的使用说明在这里

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM