Java嵌套泛型类型

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

请注意以下工作,并且无论如何这三种方法具有相同的删除类型。
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<?>>();
    
    // 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 ,但这样做我们已经失去了对它们元素的了解。
  • 当我们使用 ? extends , List拥有 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它已经在里面了,我们不知道那些 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是必要的

    你可能知道"concrete"泛型类型具有不变性,即 List<Dog> is not a subtype of 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;
    

    因此,将这些想法应用于嵌套 List :
  • List<String>List<?> 的子类型但是 List<List<String>>不是 List<List<?>> 的子类型.如前所述,这可以防止我们通过向 List 添加错误的元素而危及类型安全。 .
  • List<List<String>>List<? extends List<?>> 的子类型,因为有界通配符允许协方差。即,? extends允许 List<String> 的事实是 List<?> 的子类型被考虑。
  • List<? extends List<?>>实际上是一个共享父类(super class)型:
         List<? extends List<?>>
              ╱          ╲
    List<List<?>>    List<List<String>>
    

  • 审核中
  • Map<Integer, List<String>>只接受 List<String>作为一个值。
  • Map<?, List<?>>接受任何 List作为一个值。
  • Map<Integer, List<String>>Map<?, List<?>>是具有不同语义的不同类型。
  • 一个不能转换为另一个,以防止我们以不安全的方式进行修改。
  • Map<?, ? extends List<?>>是一个共享父类(super class)型,它施加了安全限制:
            Map<?, ? extends List<?>>
                 ╱          ╲
    Map<?, List<?>>     Map<Integer, List<String>>
    


  • 泛型方法的工作原理

    通过在方法上使用类型参数,我们可以断言 List有一些具体的类型。
    static <E> void test(Map<?, List<E>> m) {}
    

    此特定声明要求所有 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;
    }
    

    我们无法拨打 putm因为我们不知道它的 key 类型是什么了。但是,我们可以操纵它的值,因为我们知道它们都是 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以及其中的一切。

    也可以看看
  • "Java Generics: What is PECS?" ? extends之间的区别和 ? super .
  • JLS 4.10.2. Subtyping among Class and Interface TypesJLS 4.5.1. Type Arguments of Parameterized Types获取此答案的技术细节的入口点。
  • 关于Java嵌套泛型类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22806202/

    相关文章:

    java - jList - 添加元素并显示字符串?

    java - 如何测量每个输入文件的 Xtend 翻译时间

    java - 几乎循环类型绑定(bind)的递归类型参数

    带有可选值的 Java 8 泛型集合

    java - 在 Java 中,通配符能做什么常规泛型不能做什么?

    java - Java中的最优路径(tsp)

    java - 在 JBoss 中使用多个登录模块

    c# - 无法处理泛型中的 IEnumerable<int?>

    c# - 如何通过类扩展延迟初始化泛型 List<T>

    java - Object 类真的可以是下界吗?