Java嵌套泛型类型 [英] Java nested generic type

查看:156
本文介绍了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){}

//不会编译
// public static void test(Map<?,List<>> m){}


$ b $注意下面的例子,以及三种方法都有相同的擦除类型。

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


解决方案<基本上, List< List<>> List<?  extends  List<>> ; 具有不同的类型参数。



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

了解语义差异



一般而言,通配符代表一些缺失的信息。这意味着这里有一个类型参数,但我们不知道它是什么。因为我们不知道它是什么,所以我们会限制如何使用任何引用特定类型参数的东西。



现在,让我们简化例如通过使用 List 而不是 Map




  • A List< List<>> 拥有任何种类的具有任何类型参数的List 。即:

      List< List<>> theAnyList = new ArrayList< List<>>(); 

    //我们可以这样做
    theAnyList.add(new ArrayList< String>());
    theAnyList.add(new LinkedList< Integer>());

    列表<?> typeInfoLost = theAnyList.get(0);
    //但我们无法执行此
    typeInfoLost.add(new Integer(1));

    我们可以将 List 放入 theAnyList ,但是通过这样做,我们已经失去了元素的知识


  • 当我们使用?  extends 时, List 包含List的某个特定子类型,不知道它是什么了。也就是说:

     列表< ;?扩展List< Float>> theNotSureList = 
    new ArrayList< ArrayList< Float>>();

    //我们仍然可以使用它的元素
    //因为我们知道他们存储了浮点数
    List< Float> aFloatList = theNotSureList.get(0);
    aFloatList.add(new Float(1.0f));

    //但我们无法执行此操作
    theNotSureList.add(new LinkedList< Float>());

    将任何内容添加到 theNotSureList 最初是 List< LinkedList< Float>> ?或a List< Vector< Float>>

  • &NBSP;延伸&NBSP;列表与LT;?>> 。我们不知道它有什么类型的 List 它已经存在了,我们不知道那些元素类型 列出 s。也就是说:

     列表< ;?扩展List<>>> theReallyNotSureList; 

    //这些都很好
    theReallyNotSureList = theAnyList;
    theReallyNotSureList = theNotSureList;

    //但我们无法执行此操作
    theReallyNotSureList.add(new Vector< Float>);
    //以及这个
    theReallyNotSureList.get(0).add(a String);

    我们已经失去了关于 theReallyNotSureList的 以及里面的 List s的元素类型。



    (但您可能会注意到,我们可以将任何种类的列表保存到列表中......)




    所以要分解它:

      // ┌适用于外部列表
    //▼
    列表< ;?扩展List<>>>
    //▲
    //└适用于内部列表

    Map 的工作方式相同,只是有更多类型参数:

      //┌映射K参数
    //│┌映射V参数
    //▼▼
    映射<?,?扩展List<>>>
    //▲
    //└列表E参数



    为什么?  extends 是必须的



    您可能知道concrete泛型类型具有 invariance ,即 List< Dog> 不是即使 class  Dog扩展  Animal ,子类型列表< Animal> 。相反,通配符是我们的协变,也就是 List< Dog> 是<$ em的子类型c $ c> List<?  extends  Animal>

      // Dog is a动物的子类型
    class Animal {}
    class Dog extends Animal {}

    // List< Dog>是List的子类型?延伸动物>
    列表< ;?延伸动物> a = new ArrayList< Dog>();

    //所有参数化列表是List<>的子类型。
    列表<?> b = a;

    因此,将这些想法应用于嵌套的 List


    $ b

    • List< String> 列表< / code>但 List< List< String>> 不是<$ c $的子类型C>列表与LT;列表与LT;?>> 。如前所示,这样可以防止我们通过向 List 添加错误元素来危害类型安全性。

    • List< ; List< String>> List的子类型<?  extends  List<>> ,因为有界通配符允许协变。也就是说,?  extends 允许 List< String> 列表<?> 来考虑。

    • List<?  extends  List<>>> ; 实际上是一个共享的超类型:

        List< ;?扩展List<>>> 
      /╲
      List< List<>>列表与LT;列表与LT;字符串>>




    回顾




    1. Map< Integer,  List< String>> 仅接受 List< String> 作为一个值。
    2. Map<?,  List<>>

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

       地图<?,?扩展List<>>> 
      ╱╲
      Map<?,List<>> Map< Integer,List< String>>







    < h3>泛型方法如何工作

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

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

    Map 中的 all List s具有相同的元素类型。我们不知道这个类型实际上是 ,但是我们可以以抽象的方式使用它。这允许我们执行盲目操作。

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

      static< E>列表与LT E  - 代替; test(Map <?,List< E>> m){
    List< E> result = new ArrayList< E>(); (列表< E>值:m.values()){
    result.addAll(value);


    }

    返回结果;

    我们不能调用 put 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  m){
    for(K key:m.keySet()){
    L list = m.get(key); (E元素:列表){
    // ...
    }
    }
    }
    p $ p>

    这既是对我们可以传递给它的宽容,也是对我们如何操纵 m 以及其中的一切。






    另见




    How come one must use the generic type Map<?, ? extends List<?>> instead of a simpler Map<?, List<?>> for the following test() method?

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

    Noting that the following works, and that the three methods have the same erased type anyways.

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

    解决方案

    Fundamentally, List<List<?>> and List<? extends List<?>> have distinct type arguments.

    It's actually the case that one is a subtype of the other, but first let's learn more about what they mean individually.

    Understanding semantic differences

    Generally speaking, the wildcard ? represents some "missing information". It means "there was a type argument here once, but we don't know what it is anymore". And because we don't know what it is, restrictions are imposed on how we can use anything that refers to that particular type argument.

    For the moment, let's simplify the example by using List instead of 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...)

    So to break it down:

    //   ┌ applies to the "outer" List
    //   ▼
    List<? extends List<?>>
    //                  ▲
    //                  └ applies to the "inner" List
    

    The Map works the same way, it just has more type parameters:

    //  ┌ Map K argument
    //  │  ┌ Map V argument
    //  ▼  ▼
    Map<?, ? extends List<?>>
    //                    ▲
    //                    └ List E argument
    

    Why ? extends is necessary

    You may know that "concrete" generic types have invariance, that is, List<Dog> is not a subtype of List<Animal> even if class Dog extends Animal. Instead, the wildcard is how we have covariance, that is, List<Dog> is a subtype of 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;
    

    So applying these ideas to a nested 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>>
      

    In review

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


    How the generic method works

    By using a type parameter on the method, we can assert that List has some concrete type.

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

    This particular declaration requires that all Lists in the Map have the same element type. We don't know what that type actually is, but we can use it in an abstract manner. This allows us to perform "blind" operations.

    For example, this kind of declaration might be useful for some kind of accumulation:

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

    We can't call put on m because we don't know what its key type is anymore. However, we can manipulate its values because we understand they are all List with the same element type.

    Just for kicks

    Another option which the question does not discuss is to have both a bounded wildcard and a generic type for the List:

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

    We would be able to call it with something like a Map<Integer, ArrayList<String>>. This is the most permissive declaration, if we only cared about the type of E.

    We can also use bounds to nest type parameters:

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

    This is both permissive about what we can pass to it, as well as permissive about how we can manipulate m and everything in it.


    See also

    这篇关于Java嵌套泛型类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆