分配给多级通配符

时间:2014-07-14 13:28:25

标签: java generics bounded-wildcard

简单的课程:

class Pair<K,V> {

}

还有一些任务:

Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>();
Collection<Pair<String,Long>> c2 = c1; // ok
Collection<Pair<String,?>> c3 = c1; // this does not compile
Collection<? extends Pair<String,?>> c4 = c1; // ok

为什么第三个子弹不能编译而第四个完全合法?

编译错误:

Type mismatch: cannot convert from Collection<Pair<String,Long>> to Collection<Pair<String,?>>

4 个答案:

答案 0 :(得分:16)

我将尝试使用两个简单的规则来解释Java泛型。这些规则足以回答你的问题,基本上足以记住几乎所有情况:

  1. 除非X<A>,否则永远不会分配两种通用类型X<B>A = B。即,默认情况下,泛型是不变的
  2. 通配符允许分配X<A>
    • X<?>
    • X<? extends T> iff A可分配给T(将规则递归应用于AT
    • X<? super T> iff T可分配给A(将规则递归应用于TA
  3. 案例c3 = c1

    在您的示例中,您尝试将Collection<Pair<String,Long>>分配给Collection<Pair<String,?>>。也就是说,在您的情况下A = Pair<String,Long>B = Pair<String,?>。由于这些类型不相等,它们不可分配;他们违反了规则1

    问题是,为什么通配符没有帮助?答案很简单:
    规则2 NOT 传递。即,X<X<A>>不能被X<X<?>>所指定,最外层必须有一个通配符;否则规则2不适用于最外层。

    案例c4 = c1

    这里,你在外部类型中有一个通配符。由于它属于外部类型,因此规则2开始执行:A = Pair<String,?>可分配给B = ? extends Pair<String,Long>(同样,因为规则2)。因此,这是合法的。

    一般方法

    以下是检查任何复杂泛型类型的方法:只需使用这两个规则逐级检查每个泛型级别。从最外层开始。一旦某个级别违反规则,您就知道该分配是非法的;如果所有级别都遵守规则,那么分配是合法的。让我们再次考虑你的类型:

    X = Collection<Pair<String,Long>>
    Y = Collection<Pair<String,?>>
    Z = Collection<? extends Pair<String,?>> 
    

    X可分配给Y吗?

    // Outermost level:
    A = Pair<String,Long>, B = Pair<String,?>
      => B is no wildcard and A != B (Rule 1), so this is illegal!
    

    X可分配给Z吗?

    // Outermost level:
    A = Pair<String,Long>, B = ? extends Pair<String,?>
      => We got a wildcard, so Rule 2 states this is legal if the inner level is legal
    // Inner level: (we have to check both parameters)
    A = String, B = String => Equal, Rule 1 applies, fine!
    A = Long, B = ? => B is wildcard, Rule 2 applies, fine!
    

    要记住的简单规则

    每个级别的通用嵌套要么必须完全相同(A=B),要么B需要包含此级别的通配符。

答案 1 :(得分:3)

首先,让我们通过删除额外的类型参数来简化代码:

Collection<List<Long>> c1 = new ArrayList<List<Long>>();
Collection<List<Long>> c2 = c1; // ok
Collection<List<?>> c3 = c1; // this does not compile
Collection<? extends List<?>> c4 = c1; // ok

我们知道List<? extends T>本质上意味着“List你可以从T获得List<?>,而List<? extends Object>List是一样的。

因此,上述变量的类型可以解释如下:

  • c3:“Object的集合,可让您从中获取List
  • c4:“一个允许您获取Object的集合,可让您从中获取// The following line compiles, // because `ArrayList<String>` is a `List` you can get `Object`s from c3.add(new ArrayList<String>()); // The following line does not compile, // because type of c4 doesn't allow you to put anything into it c4.add(new ArrayList<String>());

特别是,这种解释会产生以下结果:

c3 = c1

现在,c3.add(new ArrayList<String>())被允许,您可以看到c1会破坏Collection<List<Long>> c1 = new ArrayList<List<Long>>(); Collection<List<?>> c3 = c1; c3.add(Arrays.asList("foo")); for (List<Long> l: c1) { for (Long value: l) { // Oops, value is not a Long! } } 的类型安全性:

{{1}}

答案 2 :(得分:2)

要记住的关键是嵌套通配符不能捕获

这意味着&#34;正常&#34;您期望从顶级通配符(即通配符代表一个特定某些东西)的行为不适用于嵌套通配符。相反,嵌套通配符代表任何类型。

例如,请接受此声明:

List<?> l;

这意味着l是一个特定类型的List。容易。

但是这个怎么样?

Collection<List<?>> c;

CollectionList个特定类型。这是CollectionList s,,其中是一种特定类型。

例如,你期待这样的事情发生:

Collection<List<?>> c = new ArrayList<List<Long>>(); // Not valid, but pretend it is
c.add(new ArrayList<Long>()); // Valid
c.add(new ArrayList<Integer>()); // Invalid, because c is a Collection of Lists of Long

但请考虑一下:

List<?> l = new ArrayList<String>();
c.add(l); // Should this compile?

l的类型与c的类型参数完全匹配,对吧?因此,即使l不是c,您也不应该将l添加到List<Long>吗?

还要考虑这个:

c.iterator().next(); // Assume there is an element to return

这应该返回什么类型? iterator()会返回Iterator<E>next()会返回E,这意味着...... c.iterator().next()会返回List<?>。这不是您期望的List<Long>。那是为什么?

因为嵌套通配符不能捕获。这是这里的关键区别。 List<?>中的通配符不会捕获单个类型&#34;整体&#34;。它为Collection 中的每个元素捕获单个类型

因此,这是完全有效的代码:

Collection<List<?>> odd = new ArrayList<List<?>>();
odd.add(new ArrayList<String>());
odd.add(new ArrayList<Long>());
List<?> l = odd.iterator().next();
        // returns the ArrayList<String>, but because odd is parameterized with
        // List<?> we can technically end up with a list of anything

记住这一点,让我们看看你的例子。


Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>();
Collection<Pair<String,Long>> c2 = c1;

这很直观。这些类型完全匹配,因此c1可分配给c2


Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>();
Collection<Pair<String,?>> c3 = c1;

现在,让我们回顾一下。 Collection<Pair<String,?>> Collection Pair StringCollectionPair个{1}}。它是String// Assume an appropriate object was assigned to c3 Pair<String, ?> p1 = new Pair<String, String>("Hello", "World"); Pair<String, ?> p2 = new Pair<String, List<String>>("Lorem", new ArrayList<>()); Pair<String, ?> p3 = new Pair<String, Map<String, Integer>>("Ispum", new HashMap<>()); c3.add(p1); c3.add(p2); c3.add(p3); 的{​​{1}},每个都是一对c3和一些未知类型,它们可能与另一对类型相同或不同采集。所以这是有效的:

c1

因为这对c1有效,但不应对c3有效,因此不允许ArrayList<Pair<String, Long>>分配Pair<String, Long>,因为它会允许您将内容放入Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>(); Collection<? extends Pair<String,?>> c4 = c1; Pair<String, ?>,而不是List<?>


List<Integer>

现在,这有点棘手。顶级捕获一个扩展Pair<String, Long>特定类型。由于通配符是特定类型的超类型(例如? extends Pair<String, ?>Pair<String, Long>的超类型),? extends Pair<String, ?>可以捕获{{1}},因为前者扩展了后者。因此,由于{{1}}与{{1}}分配兼容,因此分配有效。


从这里的各种答案可以看出,有多种方法可以解释嵌套通配符的行为。我想要一个直观的解释,我希望我能实现。

答案 3 :(得分:1)

Java泛型的一个优点是在编译时强化类型检查。所以在泛型中声明的任何内容都应完全匹配。为了使它成为一个简单的答案,我会使用一些例子。

初始化您可以喜欢的数字列表。

List<Number> numbers = new ArrayList<Number>();

这在技术上意味着List“numbers”可以存储任何数字或子类的对象。但是我们不能用子类型初始化这个列表。通用标记<>中给出的任何内容都应与字符匹配。 (Java 7提供了类型推断)

List<Number> intNumbers = new ArrayList<Interger>(); // Compile Error
List<Number> doubleNumbers = new ArrayList<Double>(); // Compile Error
List<List<String>> list = new ArrayList<ArrayList<String>>(); // Compile Error

即使Integer和Double是Number的子类,泛型也会阻止这些初始化。 <>中指定的泛型参数应完全匹配。

现在如果赋值紧密绑定,数字列表如何存储子类对象?答案是add(), addlAll().. etc方法接受E或任何扩展E的方法,其中E是我们提供的泛型类型。因此,如果列表“数字”E是Number,那么以下陈述完全有效。

List<Integer> intNumbers = new ArrayList<Integer>();
List<Number> numbers = new ArrayList<Number>();
numbers.add(new Integer(1));
numbers.add(new Double(1.0));
numbers.addAll(intNumbers);

另外一个例外是通配符。通配符用于接受任何参数。因此,以下陈述是有效的。

List<?> numbers = new ArrayList<Number>();
Map<?, ?> unKnownMap = new HashMap<String, String>();

// OR

List<Integer> intNumbers = new ArrayList<Integer>();
List<?> numbers = intNumbers;

类似地

Pair<String,?> p = new Pair<String, Long>(); // Is valid.

但是现在我们无法向此列表添加任何内容,因为元素应该扩展?并且这是一个未知类型,唯一允许的元素是null,它是每种类型的成员。 Java不会从赋值中推断出这一点。所以下面的结果是错误的

List<?> numbers = new ArrayList<Number>(); // Works fine.
numbers.add(new Integer(1)); // Compile Error
numbers.add(new Double(1.0)); // Compile Error

此外,通配符推断仅在一个级别上发生。任何嵌套都应该像通常的泛型一样完全匹配。所以

List<List<?>> list = new ArrayList<List<String>>(); // Compile error because nested List<String> doesn not exactly match List<?>
List<List<?>> list = new ArrayList<List<?>>(); // OK - Valid 
List<List<Integer>> intList = new ArrayList<List<Integer>>();
List<List<?>> numbers = intList; // Compile error because nested List<Integer> doesn not exactly match List<?>

所以这是你的情况

Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>();
Collection<Pair<String,Long>> c2 = c1; // ok
Collection<Pair<String,?>> c3 = c1; // Compile error because nested Pair<String,?> doesn not exactly match Pair<String,Long>

所以你可以做类似

的事情
Collection<Pair<String,?>> c3 = new ArrayList<Pair<String,?>>(c2);
//OR
Collection<Pair<String,?>> c3 = new ArrayList<Pair<String,?>>();
c3.addAll(c2);

案例4:当你再次说出List<? extends List<?>>时,它是第一级。这意味着它会检查所讨论的元素是否扩展List<?>

List<? extends List<?>> list = new ArrayList<List<String>>(); // Works similar to List<?> l = new ArrayList<String>();
List<? extends List<List<?>>> numbers = new ArrayList<List<List<String>>>(); // Compile error - Nested level similar to List<List<String>> lst = new ArrayList<List<String>>();

因此Collection<Pair<String,?>> c3 = new ArrayList<Pair<String, Long>>();Pair<String,?> p = new Pair<String, Long>();

类似