泛型方法上的多个通配符使Java编译器(和我!)非常混淆

时间:2010-08-23 10:37:46

标签: java generics wildcard compiler-errors

让我们首先考虑一个简单的场景(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!
    }
}

这两个通配符不相关,因此您可以使用doNothingList<String>来呼叫List<Integer>。换句话说,两个?可以指代完全不同的类型。因此,以下内容无法编译,这是预期的(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 ?>)
    }
}

到目前为止一切顺利,但这里的事情开始变得非常混乱(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<List<Integer>> lolList<String> list这两个不相关的通配符来自TwoListsOfUnknowns吗?

事实上,对该方向的以下微小修改无法编译,这是预期的(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 ?>)
    }
}

所以看起来编译器正在完成它的工作,但后来我们得到了它(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<Integer>> lol和一个List<Float> list,所以这不应该编译,对吗?

事实上,让我们回到更简单的LOLUnknowns1(两个无界的通配符)并试着看看我们是否可以以任何方式调用probablyIllegal。让我们首先尝试“简单”案例,并为两个通配符选择相同的类型(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<List<Integer>> lolList<String> list也会产生类似的编译错误!实际上,从我的实验来看,代码编译的唯一方法是第一个参数是显式的null类型(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,问题是:

  • probablyIllegal接受哪些类型的参数?
  • lol.add(list);应该编译吗?它是安全的吗?
  • 这是编译器错误还是我误解了通配符的捕获转换规则?

附录A:Double LOL?

如果有人好奇,这会编译好(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:嵌套通配符 - 它们的真正含义是什么???

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

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<String>>可能不是List<List<?>>。事实上,虽然任何List<E>都是List<?>,但看起来List<List<E>>不是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<List<?>>

3 个答案:

答案 0 :(得分:67)

答案 1 :(得分:2)

  • 不接受带有泛型的参数。在LOLUnknowns1b的情况下,null被接受,就好像第一个参数被输入List一样。例如,这会编译:

    List lol = null;
    List<String> list = null;
    probablyIllegal(lol, list);
    
  • 恕我直言lol.add(list);甚至不应该编译,但由于lol.add()需要List<?>类型的参数,并且列表符合List<?>它的作用。
    一个让我想到这个理论的奇怪例子是:

    static void probablyIllegalAgain(List<List<? extends Number>> lol, List<? extends Integer> list) {
        lol.add(list); // compiles fine!!! how come???
    }
    

    lol.add()需要List<? extends Number>类型的参数,列表的类型为List<? extends Integer>,它适合。如果不匹配则无效。 对于双LOL和其他嵌套通配符,只要第一次捕获与第二次捕获相匹配,一切都没问题(并且不可能)。

  • 同样,我不确定,但它确实看起来像是一个错误。

  • 我很高兴不是唯一一直使用lol变量的人。

资源:
http://www.angelikalanger.com,关于泛型的常见问题解答

编辑:

  1. 添加了关于Double Lol的评论
  2. 嵌套通配符。

答案 2 :(得分:0)

不是专家,但我想我能理解。

让我们将您的示例更改为等效的,但具有更多区别类型:

static void probablyIllegal(List<Class<?>> x, Class<?> y) {
    x.add(y); // this compiles!! how come???
}

让我们将List更改为[]更具启发性:

static void probablyIllegal(Class<?>[] x, Class<?> y) {
    x.add(y); // this compiles!! how come???
}

现在,x 某些类的数组。它是任何类的数组。它可以包含Class<String> a Class<Int>。这不能用普通类型参数表示:

static<T> void probablyIllegal(Class<T>[] x  //homogeneous! not the same!

Class<?>任何 Class<T>的超级T类型。如果我们认为类型对象集 set Class<?>是所有集合Class<T>的{​​{1}}。 (它包括itselft吗?我不知道......)