泛型方法上的多个通配符使Java编译器(和我!)非常困惑
让我们首先考虑一个简单的场景(参见 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!
}
}
这两个通配符是不相关的,这就是为什么您可以使用 a 和 a 进行调用的原因。换句话说,两者可以指完全不同的类型。因此,以下内容不会编译,这是可以预料的(也是在 ideone.com):doNothing
List<String>
List<Integer>
?
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 所示):
import java.util.*;
public class LOLUnknowns1 {
static void probablyIllegal(List<List<?>> lol, List<?> list) {
lol.add(list); // this compiles!! how come???
}
}
上面的代码在Eclipse中为我编译,ideone.com,但是应该吗?难道我们不可能有 a 和 a,从中类似的两个不相关的通配符情况?sun-jdk-1.6.0.17
List<List<Integer>> lol
List<String> list
TwoListsOfUnknowns
事实上,以下对该方向的轻微修改不会编译,这是可以预料的(如 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 所示):
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???
}
}
同样,我们可能有例如a和a,所以这不应该编译,对吧?List<List<Integer>> lol
List<Float> list
事实上,让我们回到更简单的(两个无界通配符),并尝试看看我们是否真的可以以任何方式调用。让我们先尝试“简单”的情况,并为两个通配符选择相同的类型(如 ideone.com 所示):LOLUnknowns1
probablyIllegal
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>)
}
}
这是没有道理的!在这里,我们甚至没有尝试使用两种不同的类型,它也不会编译!使它成为一个,也给出了类似的编译错误!事实上,从我的实验来看,代码编译的唯一方法是第一个参数是显式类型(如 ideone.com 所示):List<List<Integer>> lol
List<String> list
null
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?
如果有人好奇,这可以编译得很好(如 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<?>>
}
}
所以它看起来也许a不是一个.事实上,虽然 any 是 a ,但它看起来不像 any 是 a (如 ideone.com 所示):List<List<String>>
List<List<?>>
List<E>
List<?>
List<List<E>>
List<List<?>>
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<?>>