让我们首先考虑一个简单的场景(请参阅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使用a List<String>和a 进行调用的原因List<Integer>。换句话说,两者?可以指完全不同的类型。因此,以下内容无法编译,这是可以预期的(同样在ideone.com上):
a 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和sun-jdk-1.6.0.17ideone.com 上为我编译,但是可以吗?我们不可能有一个List<List<Integer>>lol和一个List<String> list类似的两个不相关的通配符情况TwoListsOfUnknowns吗?
List<List<Integer>>
ist<String>
实际上,朝该方向进行的以下轻微修改无法编译,这是可以预期的(如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 List<List<Integer>> lol和a List<Float> list,所以不应该编译,对吗?
List<List<Integer>> lol
a List<Float> list
实际上,让我们回到较简单的示例LOLUnknowns1(两个无界通配符),并尝试看看我们是否实际上可以probablyIllegal以任何方式调用。让我们先尝试“简单”情况,然后为两个通配符选择相同的类型(如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类型(如ideone.com所示):
List<String> list
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:双重大声笑? 万一有人好奇,可以很好地编译(如ideone.com上所示):
probablyIllegal
lol.add(list)
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 List<List<String>>不是List<List<?>>。实际上,尽管any List<E>是a List<?>,但看起来并不像any List<List<E>>是a List<List<?>>(如ideone.com所示):
a List<List<String>>
List<List<?>>
any List<E>
a List<?>
any List<List<E>>
a 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<?>>?
如附录B所示,这与多个通配符无关,而是误解了List<List<?>>真正的含义。
让我们首先提醒自己,Java泛型是不变的意味着什么:
Integer
Number
List<Number>
List<? extends Number>
现在,我们简单地将相同的参数应用于嵌套列表情况(有关更多详细信息,请参见附录):
List<String>
<?>
List<List<String>>
<List<?>>
List<? extends List<?>>
有了这种理解,就可以解释问题中的所有片段。所以产生了困惑中(错误地)认为像一个类型List<List<?>>可以捕获类型,如List<List<String>>,List<List<Integer>>等,这是不正确的。
List<List<String>>,List<List<Integer>>
即List<List<?>>:
是不是一个列表,其元素是某一个未知类型的列表。 …那将是一个 List<? extends List<?>> 相反,它是一个列表,其元素是ANY类型的列表。 片段 以下是说明以上几点的代码段:
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!!
回到问题 回到问题中的代码片段,以下行为符合预期(如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。
lol.add(list);
List<List<String>> lol
List<Object> list
问题中的所有probablyIllegal方法都不是非法的。它们都是完全合法和类型安全的。编译器中绝对没有错误。它确实在做应该做的事情。