让我们首先考虑一个简单的场景(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中编译,在ideone.com中编译sun-jdk-1.6.0.17
,但它应该是吗?我们不可能有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);
应该编译吗?它是安全的吗?如果有人好奇,这会编译好(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);
}
}
进一步调查表明,可能有多个通配符与此问题无关,而是嵌套通配符是混乱的根源。
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<?>>
?
答案 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,关于泛型的常见问题解答
编辑:
答案 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吗?我不知道......)