Multiple wildcards on a generic methods makes Java compiler (and me!) very confused
让我们首先考虑一个简单的场景(参见ideone.com上的完整源代码):
1 2 3 4 5 6 7 8 9 10 11 | 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! } } |
这两个通配符是不相关的,这就是为什么可以用
1 2 3 4 5 6 7 8 9 10 | 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上所见):
1 2 3 4 5 6 7 | import java.util.*; public class LOLUnknowns1 { static void probablyIllegal(List<List<?>> lol, List<?> list) { lol.add(list); // this compiles!! how come??? } } |
上面的代码在eclipse和ideone.com中为我编译,但是应该这样吗?难道我们不可能有一个> lol
事实上,对该方向的以下细微修改并未编译,这是可以预料的(如ideone.com上所见):
1 2 3 4 5 6 7 8 9 10 11 12 | 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上看到的):
1 2 3 4 5 6 7 8 9 | 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??? } } |
同样,我们可能有一个> lol
实际上,让我们回到更简单的EDOCX1(两个无界通配符),看看我们是否可以以任何方式调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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>) } } |
这没道理!这里我们甚至不尝试使用两种不同的类型,而且它也不编译!使其成为> lol
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 } } |
因此,关于
probablyIllegal 接受什么类型的论点?lol.add(list); 应该编译吗?是打字机吗?- 这是编译器错误还是我误解了通配符的捕获转换规则?
附录A:双重LOL?
如果有人好奇的话,这是很好的汇编(如ideone.com上所见):
1 2 3 4 5 6 7 8 9 | 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:嵌套通配符——它们的真正含义是什么????
进一步的调查表明,也许多个通配符与此问题无关,但更确切地说,嵌套通配符是混淆的根源。
1 2 3 4 5 6 7 8 9 10 11 12 | 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<?>> } } |
所以看起来,一个>
>
>
>
1 2 3 4 5 6 7 8 9 10 11 12 | 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<?>> } } |
于是,一个新的问题出现了:究竟什么是>
如附录B所示,这与多个通配符无关,而是误解了>
让我们首先提醒一下,Java泛型是不变的:
现在,我们只需将相同的参数应用于嵌套列表情况(有关更多详细信息,请参阅附录):
- >
- >
- >
有了这种理解,就可以解释问题中的所有片段。混淆产生于(错误地)相信像>
>
>
也就是说,一个>
- 不是其元素是某个未知类型列表的列表。
- …那将是一个
List extends List>> 。
- …那将是一个
- 相反,它是一个元素是任何类型列表的列表。
片断
下面是一个片段来说明以上几点:
1 2 3 4 5 6 7 8 9 10 11 | List<List<?>> lolAny = new ArrayList<List<?>>(); lolAny.add(new ArrayList<Integer>()); lolAny.add(new ArrayList<String>()); // lolAny = new ArrayList<List<String>>(); // DOES NOT COMPILE!! List<? extends List<?>> lolSome; lolSome = new ArrayList<List<String>>(); lolSome = new ArrayList<List<Integer>>(); |
更多片段
下面是另一个使用有界嵌套通配符的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 | List<List<? extends Number>> lolAnyNum = new ArrayList<List<? extends Number>>(); lolAnyNum.add(new ArrayList<Integer>()); lolAnyNum.add(new ArrayList<Float>()); // lolAnyNum.add(new ArrayList<String>()); // DOES NOT COMPILE!! // lolAnyNum = new ArrayList<List<Integer>>(); // DOES NOT COMPILE!! List<? extends List<? extends Number>> lolSomeNum; lolSomeNum = new ArrayList<List<Integer>>(); lolSomeNum = new ArrayList<List<Float>>(); // lolSomeNum = new ArrayList<List<String>>(); // DOES NOT COMPILE!! |
回到问题上来
要返回到问题中的代码片段,以下行为如预期(如ideone.com上所示):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class LOLUnknowns1d { static void nowDefinitelyIllegal(List<? extends List<?>> lol, List<?> list) { lol.add(list); // DOES NOT COMPILE!!! // The method add(capture#1-of ? extends List<?>) in the // type List<capture#1-of ? extends List<?>> is not // applicable for the arguments (List<capture#3-of ?>) } public static void main(String[] args) { List<Object> list = null; List<List<String>> lolString = null; List<List<Integer>> lolInteger = null; // these casts are valid nowDefinitelyIllegal(lolString, list); nowDefinitelyIllegal(lolInteger, list); } } |
> lol
所有的
- Angelika Langer的Java泛型常见问题解答
- 在泛型类型的实例化之间存在哪些超级子类型关系?
- 我可以创建一个类型是通配符参数化类型的对象吗?
- JLS 5.1.10捕获转换
相关问题
- 有什么简单的方法来解释为什么我不能做
List ?animals = new ArrayList () - Java嵌套通配符通用不能编译
附录:捕获转换规则
(This was brought up in the first revision of the answer; it's a worthy supplement to the type invariant argument.)
5.1.10 Capture Conversion
Let G name a generic type declaration with n formal type parameters A1…An with corresponding bounds U1…Un. There exists a capture conversion from G
1…Tn> to G 1…Sn>, where, for 1 <= i <= n:If Ti is a wildcard type argument of the form ? then …If Ti is a wildcard type argument of the form ? extends Bi, then …If Ti is a wildcard type argument of the form ? super Bi, then …Otherwise, Si = Ti. Capture conversion is not applied recursively.
这一节可能会令人困惑,特别是关于捕获转换(此处为cc)的非递归应用,但关键是并非所有
让我们通过几个简单的例子:
List> 可与List 配套? 可以按规则1进行CC
List extends Number> 可与List 配套? 可按规则2进行CC。- 在应用规则2时,biis simply
Number 。
List extends Number> 不能抄送List 。? 可以按规则2进行CC,但由于类型不兼容而发生编译时错误。
现在让我们尝试一些嵌套:
List 不能抄送- >
List 。- >
- 规则4适用,CC不是递归的,所以
? 不能CC
- 规则4适用,CC不是递归的,所以
List extends List>> 可CCList 号- >
- 第一个
? 可以按规则2进行CC - 在应用规则2时,biis now a
List> ,which can ccList 。 - 两个
? CAN CC
- 第一个
List extends List extends Number>> 可CCList 号- >
- 第一个
? 可以按规则2进行CC - 在应用规则2时,biis now a
List extends Number> ,which can ccList 。 - 两个
? CAN CC
- 第一个
List extends List extends Number>> 不能抄送List 。- >
- 第一个
? 可以按规则2进行CC - 在应用规则2时,biis now a
List extends Number> ,which can cc,but gives a compile time error when applied toList 。 - 两个
? CAN CC
- 第一个
为了进一步说明为什么有些
1 2 3 4 | // WildSnippet1 new HashMap<?,?>(); // DOES NOT COMPILE!!! new HashMap<List<?>, ?>(); // DOES NOT COMPILE!!! new HashMap<?, Set<?>>(); // DOES NOT COMPILE!!! |
但是,下面的编译很好:
1 2 3 | // WildSnippet2 new HashMap<List<?>,Set<?>>(); // compiles fine! new HashMap<Map<?,?>, Map<?,Map<?,?>>>(); // compiles fine! |
不应接受带有泛型的论点。在
LOLUnknowns1b 的情况下,null 被接受,就好像第一个参数被键入List 一样。例如,这确实编译了:1
2
3imho
lol.add(list); 甚至不应该编译,但由于lol.add() 需要一个List> 类型的参数,并且清单适合List> 类型的参数,所以它可以工作。让我想到这个理论的一个奇怪的例子是:1
2
3static void probablyIllegalAgain(List<List<? extends Number>> lol, List<? extends Integer> list) {
lol.add(list); // compiles fine!!! how come???
}lol.add() 需要一个List extends Number> 类型的参数,list类型为List extends Integer> 类型,它适合。如果不匹配,就不起作用。对于double lol和其他嵌套的通配符来说,同样的事情,只要第一个捕获与第二个捕获匹配,一切都正常(而souldn不正常)。再说一次,我不确定,但它看起来确实像个虫子。
我很高兴不是唯一一个一直使用
lol 变量的人。
资源:http://www.angelikalanger.com,关于仿制药的常见问题解答
编辑:
不是专家,但我想我能理解。
让我们将您的示例改为等效的,但具有更多区别的类型:
1 2 3 | static void probablyIllegal(List<Class<?>> x, Class<?> y) { x.add(y); // this compiles!! how come??? } |
让我们将列表更改为[]以更具启发性:
1 2 3 | static void probablyIllegal(Class<?>[] x, Class<?> y) { x.add(y); // this compiles!! how come??? } |
现在,x不是某种类型的类的数组。它是任何类型的类的数组。它可以包含一个
1 | static<T> void probablyIllegal(Class<T>[] x //homogeneous! not the same! |