让我们首先考虑一个简单的场景( 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!
}
}
这两个通配符是不相关的,这就是为什么您可以调用
doNothing
与 List<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 和
sun-jdk-1.6.0.17
上为我编译在 ideone.com 中,但应该吗?我们不可能有 List<List<Integer>> lol
和一个 List<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>> lol
和 List<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
}
}
所以问题是,关于
LOLUnknowns1
, LOLUnknowns1a
和 LOLUnknowns1b
:probablyIllegal
接受? lol.add(list);
完全编译?它是类型安全的吗? 附录A:双重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<?>>
?
最佳答案
如附录 B 所示,这与多个通配符无关,而是误解了 List<List<?>>
真正意思。
让我们首先提醒自己 Java 泛型是不变的意味着什么:
Integer
是 Number
List<Integer>
不是 List<Number>
List<Integer>
是 List<? extends Number>
我们现在简单地将相同的参数应用于我们的嵌套列表情况(有关更多详细信息,请参见附录):
List<String>
是(可被捕获)List<?>
List<List<String>>
不是(可被捕获)List<List<?>>
List<List<String>>
IS(可被捕获)List<? extends List<?>>
有了这种理解,就可以解释问题中的所有片段。混淆产生于(错误地)相信像
List<List<?>>
这样的类型。可以捕获类似 List<List<String>>
的类型, List<List<Integer>>
等。这不是真的。也就是说,一个
List<List<?>>
:List<? extends List<?>>
片段
下面是一个片段来说明以上几点:
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>>();
更多片段
这是另一个带有有界嵌套通配符的示例:
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!!
回到问题
回到问题中的片段,以下行为符合预期( as seen on 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<String>> lol
和一个 List<Object> list
.事实上,如果我们注释掉有问题的语句,代码就会编译,这正是我们在 main
中的第一次调用时所做的。 .所有的
probablyIllegal
问题中的方法不违法。它们都是完全合法且类型安全的。编译器绝对没有错误。它正在做它应该做的事情。引用
相关问题
List<Animal> animals = new ArrayList<Dog>()
? 附录:捕获转换规则
(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<T1…Tn> to G<S1…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),但关键是并非所有
?
可以抄送;这取决于它出现的位置。规则 4 中没有递归应用,但是当规则 2 或 3 应用时,相应的 Bi 本身可能是 CC 的结果。让我们来看看几个简单的例子:
List<?>
可以抄送List<String>
?
可以按规则 1 抄送 List<? extends Number>
可以抄送List<Integer>
?
可以按规则 2 抄送 Number
List<? extends Number>
不能抄送 List<String>
?
可以按规则 2 CC,但由于类型不兼容而导致编译时错误 现在让我们尝试一些嵌套:
List<List<?>>
不能抄送 List<List<String>>
?
不能抄送 List<? extends List<?>>
可以抄送List<List<String>>
?
可以按规则 2 抄送 List<?>
, 可以抄送 List<String>
?
可以抄送List<? extends List<? extends Number>>
可以抄送List<List<Integer>>
?
可以按规则 2 抄送 List<? extends Number>
, 可以抄送 List<Integer>
?
可以抄送List<? extends List<? extends Number>>
不能抄送 List<List<Integer>>
?
可以按规则 2 抄送 List<? extends Number>
,它可以 CC,但在应用于 List<Integer>
时会出现编译时错误?
可以抄送进一步说明为什么有些
?
可以 CC 而其他人不能,请考虑以下规则:您不能直接实例化通配符类型。也就是说,以下给出了编译时错误: // WildSnippet1
new HashMap<?,?>(); // DOES NOT COMPILE!!!
new HashMap<List<?>, ?>(); // DOES NOT COMPILE!!!
new HashMap<?, Set<?>>(); // DOES NOT COMPILE!!!
但是,以下编译得很好: // WildSnippet2
new HashMap<List<?>,Set<?>>(); // compiles fine!
new HashMap<Map<?,?>, Map<?,Map<?,?>>>(); // compiles fine!
原因WildSnippet2
编译是因为,如上所述,没有 ?
可以抄送。在 WildSnippet1
, 要么 K
或 V
(或两者)HashMap<K,V>
can CC,通过new
直接实例化非法的。
关于java - 泛型方法上的多个通配符使 Java 编译器(和我!)非常困惑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3546745/