泛型方法上的多个通配符使 Java 编译器(和我!)非常困惑 [英] Multiple wildcards on a generic methods makes Java compiler (and me!) very confused
问题描述
让我们首先考虑一个简单的场景(查看 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!
}
}
这两个通配符不相关,这就是为什么您可以使用 List
和 List
调用 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
和一个 >哈哈
List
,来自 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
和 >哈哈
List
也给出了类似的编译错误!事实上,根据我的实验,代码编译的唯一方法是第一个参数是显式 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
}
}
所以问题是,关于LOLUnknowns1
、LOLUnknowns1a
和LOLUnknowns1b
:
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:
- 一个
Integer
是一个Number
- A
List
是 NOTList
- A
List
IS 一个List
- An
Integer
is aNumber
- A
List<Integer>
is NOT aList<Number>
- A
List<Integer>
IS aList<? extends Number>
我们现在只需将相同的参数应用于我们的嵌套列表情况(更多细节见附录):
We now simply apply the same argument to our nested list situation (see appendix for more details):
- A
List
是(可捕获的)List>
- A
List
是 NOT(可由)- >
List
- >
- A
List
IS(可由)- >
List>
- A
List<String>
is (captureable by) aList<?>
- A
List<List<String>>
is NOT (captureable by) aList<List<?>>
- A
List<List<String>>
IS (captureable by) aList<? 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
:
- 是NOT一个列表,其元素是某种未知类型的列表.
- ... 那将是一个
List>
- is NOT a list whose elements are lists of some one unknown type.
- ... that would be a
List<? extends List<?>>
这里有一个片段来说明以上几点:
Here's a snippet to illustrate the above points:
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>>();
更多片段
这是另一个带有有界嵌套通配符的示例:
More snippets
Here's yet another example with bounded nested wildcard:
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 上看到):
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.add(list);
是非法的,因为我们可能有一个List
和一个- >哈哈
List
.事实上,如果我们注释掉有问题的语句,代码就会编译,这正是我们在main
中的第一次调用所得到的.lol.add(list);
is illegal because we may have aList<List<String>> lol
and aList<Object> list
. In fact, if we comment out the offending statement, the code compiles and that's exactly what we have with the first invocation inmain
.问题中的所有
probablyIllegal
方法都不是非法的.它们都是完全合法且类型安全的.编译器绝对没有错误.它正在做它应该做的事情.All of the
probablyIllegal
methods in the question, aren't illegal. They are all perfectly legal and typesafe. There is absolutely no bug in the compiler. It is doing exactly what it's supposed to do.- Angelika Langer 的 Java 泛型常见问题解答
(这是在答案的第一次修订中提出的;它是对类型不变参数的一个有价值的补充.)
让 G 用 n 个形式类型参数命名一个泛型类型声明 A1...An 具有相应的边界 U1...Un.存在从G
1…Tn> 到G1...Sn>,其中,对于 1 <= i <= n: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<T1…Tn> to G<S1…Sn>, where, for 1 <= i <= n:
- 如果 Ti 是
?
形式的通配符类型参数,那么…… - 如果 Ti 是
形式的通配符类型参数?扩展
Bi,然后…… - 如果 Ti 是
形式的通配符类型参数?super
Bi,然后…… - 否则,Si = Ti.
- 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.
捕获转换不会递归应用.
本节可能会令人困惑,尤其是关于捕获转换的非递归应用(特此 CC),但关键是并非所有
?
代码>可以抄送;这取决于它出现的位置.规则 4 中没有递归应用,但是当规则 2 或 3 应用时,相应的 Bi 本身可能是 CC 的结果.This section can be confusing, especially with regards to the non-recursive application of the capture conversion (hereby CC), but the key is that not all
?
can CC; it depends on where it appears. There is no recursive application in rule 4, but when rules 2 or 3 applies, then the respective Bi may itself be the result of a CC.让我们看几个简单的例子:
Let's work through a few simple examples:
List>
可以抄送List
?
可以按规则 1 抄送
List<?>
can CCList<String>
- The
?
can CC by rule 1
?
可以按规则 2 抄送- 在应用规则 2 时,Bi 就是
Number
- The
?
can CC by rule 2 - In applying rule 2, Bi is simply
Number
?
可以按照规则 2 CC,但由于类型不兼容导致编译时错误
- The
?
can CC by rule 2, but compile time error occurs due to incompatible types
现在让我们尝试一些嵌套:
Now let's try some nesting:
List
可以 NOT CC- >
List
- >
- 规则 4 适用,CC 不是递归的,所以
?
可以 NOT CC
List<List<?>>
can NOT CCList<List<String>>
- Rule 4 applies, and CC is not recursive, so the
?
can NOT CC
- 第一个
?
可以按规则2抄送 - 在应用规则 2 时,Bi 现在是一个
List>
,它可以 CCList
; ?
都可以抄送
- The first
?
can CC by rule 2 - In applying rule 2, Bi is now a
List<?>
, which can CCList<String>
- Both
?
can CC
- 第一个
?
可以按规则2抄送 - 在应用规则 2 时,Bi 现在是
List
,可以CCList
?
都可以抄送
- The first
?
can CC by rule 2 - In applying rule 2, Bi is now a
List<? extends Number>
, which can CCList<Integer>
- Both
?
can CC
- 第一个
?
可以按规则2抄送 - 在应用规则 2 时,Bi 现在是
List
,可以CC,但是当应用于List
时会出现编译时错误 ?
都可以抄送
- The first
?
can CC by rule 2 - In applying rule 2, Bi is now a
List<? extends Number>
, which can CC, but gives a compile time error when applied toList<Integer>
- Both
?
can CC
为了进一步说明为什么某些
?
可以 CC 而其他人不能,请考虑以下规则:您可以 NOT 直接实例化通配符类型.即以下给出编译时错误:To further illustrate why some
?
can CC and others can't, consider the following rule: you can NOT directly instantiate a wildcard type. That is, the following gives a compile time error:// WildSnippet1 new HashMap<?,?>(); // DOES NOT COMPILE!!! new HashMap<List<?>, ?>(); // DOES NOT COMPILE!!! new HashMap<?, Set<?>>(); // DOES NOT COMPILE!!!
但是,以下编译得很好:
However, the following compiles just fine:
// WildSnippet2 new HashMap<List<?>,Set<?>>(); // compiles fine! new HashMap<Map<?,?>, Map<?,Map<?,?>>>(); // compiles fine!
WildSnippet2
编译的原因是,如上所述,没有一个?
可以 CC.在WildSnippet1
中,HashMap
的K
或V
(或两者)都可以CC,这使得通过new
直接实例化是非法的.The reason
WildSnippet2
compiles is because, as explained above, none of the?
can CC. InWildSnippet1
, either theK
or theV
(or both) of theHashMap<K,V>
can CC, which makes the direct instantiation throughnew
illegal.这篇关于泛型方法上的多个通配符使 Java 编译器(和我!)非常困惑的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
- Rule 4 applies, and CC is not recursive, so the
- 规则 4 适用,CC 不是递归的,所以
- The
- 如果 Ti 是
- ... that would be a
- ... 那将是一个