泛型方法上的多个通配符使 Java 编译器(和我!)非常困惑 [英] Multiple wildcards on a generic methods makes Java compiler (and me!) very confused

查看:20
本文介绍了泛型方法上的多个通配符使 Java 编译器(和我!)非常困惑的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我们首先考虑一个简单的场景(查看 ideone.com 上的完整源代码):

Let's first consider a simple scenario (see complete source on ideone.com):

import java.util.*;

public class TwoListsOfUnknowns {
    static void doNothing(List<?> list1, List<?> list2) { }

    public static void main(String[] args) {
        List<String> list1 = null;
        List<Integer> list2 = null;
        doNothing(list1, list2); // compiles fine!
    }
}

这两个通配符不相关,这就是为什么您可以使用 ListList 调用 doNothing.换句话说,这两个 ? 可以指代完全不同的类型.因此,以下内容无法编译,这是意料之中的(也在 ideone.com 上):

The two wildcards are unrelated, which is why you can call doNothing with a List<String> and a List<Integer>. In other words, the two ? can refer to entirely different types. Hence the following does not compile, which is to be expected (also on ideone.com):

import java.util.*;

public class TwoListsOfUnknowns2 {
    static void doSomethingIllegal(List<?> list1, List<?> list2) {
        list1.addAll(list2); // DOES NOT COMPILE!!!
            // The method addAll(Collection<? extends capture#1-of ?>)
            // in the type List<capture#1-of ?> is not applicable for
            // the arguments (List<capture#2-of ?>)
    }
}

到目前为止一切都很好,但事情开始变得非常混乱(在 ideone.com 上看到):

So far so good, but here's where things start to get very confusing (as seen on ideone.com):

import java.util.*;

public class LOLUnknowns1 {
    static void probablyIllegal(List<List<?>> lol, List<?> list) {
        lol.add(list); // this compiles!! how come???
    }
}

以上代码在 Eclipse 和 ideone.com 中的 sun-jdk-1.6.0.17 上为我编译,但应该这样吗?难道我们有一个 List>哈哈 和一个 Listlist,来自 TwoListsOfUnknowns 的两个不相关通配符的类似情况?

The above code compiles for me in Eclipse and on sun-jdk-1.6.0.17 in ideone.com, but should it? Is it not possible that we have a List<List<Integer>> lol and a List<String> list, the analogous two unrelated wildcards situations from TwoListsOfUnknowns?

事实上,以下朝那个方向的轻微修改不会编译,这是可以预料的(如在 ideone.com 上看到的):

In fact the following slight modification towards that direction does not compile, which is to be expected (as seen on ideone.com):

import java.util.*;

public class LOLUnknowns2 {
    static void rightfullyIllegal(
            List<List<? extends Number>> lol, List<?> list) {

        lol.add(list); // DOES NOT COMPILE! As expected!!!
            // The method add(List<? extends Number>) in the type
            // List<List<? extends Number>> is not applicable for
            // the arguments (List<capture#1-of ?>)
    }
}

所以看起来编译器正在做它的工作,但后来我们得到了这个(如在 ideone.com 上看到的):

So it looks like the compiler is doing its job, but then we get this (as seen on ideone.com):

import java.util.*;

public class LOLUnknowns3 {
    static void probablyIllegalAgain(
            List<List<? extends Number>> lol, List<? extends Number> list) {

        lol.add(list); // compiles fine!!! how come???
    }
}

同样,我们可能有例如List>哈哈 和一个 List列表,所以这不应该编译,对吗?

Again, we may have e.g. a List<List<Integer>> lol and a List<Float> list, so this shouldn't compile, right?

事实上,让我们回到更简单的LOLUnknowns1(两个无界通配符)并尝试看看我们是否可以以任何方式调用probablyIllegal.让我们先尝试简单"的情况,并为两个通配符选择相同的类型(在 ideone.com 上看到):

In fact, let's go back to the simpler LOLUnknowns1 (two unbounded wildcards) and try to see if we can in fact invoke probablyIllegal in any way. Let's try the "easy" case first and choose the same type for the two wildcards (as seen on ideone.com):

import java.util.*;

public class LOLUnknowns1a {
    static void probablyIllegal(List<List<?>> lol, List<?> list) {
        lol.add(list); // this compiles!! how come???
    }

    public static void main(String[] args) {
        List<List<String>> lol = null;
        List<String> list = null;
        probablyIllegal(lol, list); // DOES NOT COMPILE!!
            // The method probablyIllegal(List<List<?>>, List<?>)
            // in the type LOLUnknowns1a is not applicable for the
            // arguments (List<List<String>>, List<String>)
    }
}

这毫无意义!在这里,我们甚至没有尝试使用两种不同的类型,而且它不会编译!使其成为 List>哈哈Listlist 也给出了类似的编译错误!事实上,根据我的实验,代码编译的唯一方法是第一个参数是显式 null 类型(如在 ideone.com 上看到的):

This makes no sense! Here we aren't even trying to use two different types, and it doesn't compile! Making it a List<List<Integer>> lol and List<String> list also gives a similar compilation error! In fact, from my experimentation, the only way that the code compiles is if the first argument is an explicit null type (as seen on ideone.com):

import java.util.*;

public class LOLUnknowns1b {
    static void probablyIllegal(List<List<?>> lol, List<?> list) {
        lol.add(list); // this compiles!! how come???
    }

    public static void main(String[] args) {
        List<String> list = null;
        probablyIllegal(null, list); // compiles fine!
            // throws NullPointerException at run-time
    }
}

所以问题是,关于LOLUnknowns1LOLUnknowns1aLOLUnknowns1b:

So the questions are, with regards to LOLUnknowns1, LOLUnknowns1a and LOLUnknowns1b:

  • probablyIllegal 接受哪些类型的参数?
  • 应该 lol.add(list); 编译吗?它是类型安全的吗?
  • 这是编译器错误还是我误解了通配符的捕获转换规则?
  • What types of arguments does probablyIllegal accept?
  • Should lol.add(list); compile at all? Is it typesafe?
  • Is this a compiler bug or am I misunderstanding the capture conversion rules for wildcards?

以防万一有人好奇,这编译得很好(在 ideone.com 上看到):

In case anyone is curious, this compiles fine (as seen on ideone.com):

import java.util.*;

public class DoubleLOL {
    static void omg2xLOL(List<List<?>> lol1, List<List<?>> lol2) {
        // compiles just fine!!!
        lol1.addAll(lol2);
        lol2.addAll(lol1);
    }
}

<小时>

附录 B:嵌套通配符——它们的真正含义是什么???

进一步调查表明,多个通配符可能与问题无关,而嵌套通配符才是混淆的根源.


Appendix B: Nested wildcards -- what do they really mean???

Further investigation indicates that perhaps multiple wildcards has nothing to do with the problem, but rather a nested wildcard is the source of the confusion.

import java.util.*;

public class IntoTheWild {

    public static void main(String[] args) {
        List<?> list = new ArrayList<String>(); // compiles fine!

        List<List<?>> lol = new ArrayList<List<String>>(); // DOES NOT COMPILE!!!
            // Type mismatch: cannot convert from
            // ArrayList<List<String>> to List<List<?>>
    }
}

所以它看起来可能 List> 不是 List.事实上,虽然任何 List 都是 List,但它看起来不像任何 List> 是一个 List>(在 ideone.com 上看到):

So it looks perhaps a List<List<String>> is not a List<List<?>>. In fact, while any List<E> is a List<?>, it doesn't look like any List<List<E>> is a List<List<?>> (as seen on ideone.com):

import java.util.*;

public class IntoTheWild2 {
    static <E> List<?> makeItWild(List<E> list) {
        return list; // compiles fine!
    }
    static <E> List<List<?>> makeItWildLOL(List<List<E>> lol) {
        return lol;  // DOES NOT COMPILE!!!
            // Type mismatch: cannot convert from
            // List<List<E>> to List<List<?>>
    }
}

一个新的问题出现了,那么:什么是List>?

A new question arises, then: just what is a List<List<?>>?

推荐答案

如附录 B 所示,这与多个通配符无关,而是误解了List代码>真正的意思.

As Appendix B indicates, this has nothing to do with multiple wildcards, but rather, misunderstanding what List<List<?>> really means.

让我们首先提醒自己 Java 泛型是不变的:

Let's first remind ourselves what it means that Java generics is invariant:

  1. 一个 Integer 是一个 Number
  2. A ListNOT List
  3. A List IS 一个 List
  1. An Integer is a Number
  2. A List<Integer> is NOT a List<Number>
  3. A List<Integer> IS a List<? extends Number>

我们现在只需将相同的参数应用于我们的嵌套列表情况(更多细节见附录):

We now simply apply the same argument to our nested list situation (see appendix for more details):

  1. A List 是(可捕获的)List
  2. A List>NOT(可由)List>
  3. A List> IS(可由)List>
  1. A List<String> is (captureable by) a List<?>
  2. A List<List<String>> is NOT (captureable by) a List<List<?>>
  3. A List<List<String>> IS (captureable by) a List<? extends List<?>>

有了这种理解,就可以解释问题中的所有片段.混淆出现在(错误地)相信像 List 这样的类型可以捕获像 List> 这样的类型>List> 等等.这是正确的.

With this understanding, all of the snippets in the question can be explained. The confusion arises in (falsely) believing that a type like List<List<?>> can capture types like List<List<String>>, List<List<Integer>>, etc. This is NOT true.

即一个List: