Java嵌套泛型类型

Lou*_*iss 25 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) {}
Run Code Online (Sandbox Code Playgroud)

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

public static <E> void test(Map<?, List<E>> m) {}
Run Code Online (Sandbox Code Playgroud)

Rad*_*def 52

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

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

理解语义差异

一般来说,通配符?代表一些"缺失的信息".这意味着"这里曾经存在类型论证,但我们不知道它是什么".而且因为我们不知道它是什么,所以对如何使用引用该特定类型参数的任何东西施加限制.

暂时,让我们使用List而不是简化示例Map.

  • A List<List<?>>包含任何类型参数的任何类型的List.所以ie:

    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) );
    
    Run Code Online (Sandbox Code Playgroud)

    我们可以把任何ListtheAnyList,但这样做,我们已经失去了的知识,它们的元素.

  • 当我们使用时? extends,List保存List的某个特定子类型,但我们不知道它是什么.所以ie:

    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>() );
    
    Run Code Online (Sandbox Code Playgroud)

    添加任何内容不再安全theNotSureList,因为我们不知道其元素的实际类型.(当时它最初是一个List<LinkedList<Float>>?还是List<Vector<Float>>?我们不知道.)

  • 我们可以将这些放在一起并有一个List<? extends List<?>>.我们不知道是什么类型的List.它有它了,我们不知道的元素类型 List小号两种.所以ie:

    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" );
    
    Run Code Online (Sandbox Code Playgroud)

    我们已经丢失有关它内部信息的信息theReallyNotSureList,以及它的元素类型List.

    (但你可能会注意到我们可以为它分配任何类型的List保持列表 ...)

所以要打破它:

//   ? applies to the "outer" List
//   ?
List<? extends List<?>>
//                  ?
//                  ? applies to the "inner" List
Run Code Online (Sandbox Code Playgroud)

Map作品以同样的方式,它只是有更多的类型参数:

//  ? Map K argument
//  ?  ? Map V argument
//  ?  ?
Map<?, ? extends List<?>>
//                    ?
//                    ? List E argument
Run Code Online (Sandbox Code Playgroud)

为什么? extends有必要

您可能知道"具体"泛型类型具有不变性,即即使List<Dog>不是子类型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;
Run Code Online (Sandbox Code Playgroud)

所以将这些想法应用于嵌套List:

  • List<String>是的一个亚型List<?>,但List<List<String>>就是没有一个亚型List<List<?>>.如前所示,这可以防止我们通过添加错误的元素来破坏类型安全性List.
  • List<List<String>> 一个子类型List<? extends List<?>>,因为有界通配符允许协方差.也就是说,? extends允许考虑List<String>作为子类型的事实List<?>.
  • List<? extends List<?>> 实际上是一个共享的超类型:

         List<? extends List<?>>
              ?          ?
    List<List<?>>    List<List<String>>
    
    Run Code Online (Sandbox Code Playgroud)

在审查中

  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>>
    
    Run Code Online (Sandbox Code Playgroud)

泛型方法如何工作

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

static <E> void test(Map<?, List<E>> m) {}
Run Code Online (Sandbox Code Playgroud)

这个特殊的声明要求所有的 List s 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;
}
Run Code Online (Sandbox Code Playgroud)

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

只是为了踢

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

static <E> void test(Map<?, ? extends List<E>> m) {}
Run Code Online (Sandbox Code Playgroud)

我们可以用类似的东西来调用它Map<Integer, ArrayList<String>>.如果我们只关心它的类型,这是最宽容的声明E.

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

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) {
            // ...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这既允许我们传递给它的东西,也允许我们如何操纵m它以及它中的所有东西.


也可以看看