pol*_*nts 58 java generics compiler-errors wildcard
让我们首先考虑一个简单的场景(请参阅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!
}
}
Run Code Online (Sandbox Code Playgroud)
这两个通配符是不相关的,这就是为什么你可以doNothing
用a List<String>
和a 调用List<Integer>
.换句话说,两者?
可以指代完全不同的类型.因此,以下不编译,这是预期的(也在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 ?>)
}
}
Run Code Online (Sandbox Code Playgroud)
到目前为止一切都那么好,但是这里的事情开始变得非常混乱(如ideone.com上所见):
import java.util.*;
public class LOLUnknowns1 {
static void probablyIllegal(List<List<?>> lol, List<?> list) {
lol.add(list); // this compiles!! how come???
}
}
Run Code Online (Sandbox Code Playgroud)
上面的代码在Eclipse和sun-jdk-1.6.0.17
ideone.com中为我编译,但它应该吗?难道我们不可能有一个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 ?>)
}
}
Run Code Online (Sandbox Code Playgroud)
所以看起来编译器正在完成它的工作,但后来我们得到了这个(如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???
}
}
Run Code Online (Sandbox Code Playgroud)
同样,我们可能有例如a 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>)
}
}
Run Code Online (Sandbox Code Playgroud)
这毫无意义!在这里,我们甚至没有尝试使用两种不同的类型,它不编译!使它成为一个List<List<Integer>> lol
并且List<String> list
也给出了类似的编译错误!实际上,从我的实验来看,代码编译的唯一方法是第一个参数是显式null
类型(如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
}
}
Run Code Online (Sandbox Code Playgroud)
所以,问题是,关于LOLUnknowns1
,LOLUnknowns1a
和LOLUnknowns1b
:
probablyIllegal
接受?lol.add(list);
编译吗?它是安全的吗?如果有人好奇,这个编译很好(如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);
}
}
Run Code Online (Sandbox Code Playgroud)
进一步调查表明,也许多个通配符与问题无关,而是嵌套通配符是混乱的根源.
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<?>>
}
}
Run Code Online (Sandbox Code Playgroud)
所以它看起来可能List<List<String>>
不是一个List<List<?>>
.事实上,虽然任何List<E>
都是a List<?>
,但它看起来并不像是List<List<E>>
一个List<List<?>>
(如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<?>>
}
}
Run Code Online (Sandbox Code Playgroud)
然后出现了一个新问题:那是List<List<?>>
什么?
pol*_*nts 70
作为附录B表示,这无关与多个通配符,而是误解是什么List<List<?>>
真正的意思.
让我们首先提醒自己Java泛型是不变的意思:
Integer
是一个Number
List<Integer>
是不是一个List<Number>
List<Integer>
是一个List<? extends Number>
我们现在只是将相同的参数应用于我们的嵌套列表情况(有关更多详细信息,请参阅附录):
List<String>
是(可以通过)aList<?>
List<List<String>>
是NOT(由可捕获)一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>>();
Run Code Online (Sandbox Code Playgroud)
这是有界嵌套通配符的另一个例子:
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!!
Run Code Online (Sandbox Code Playgroud)
要回到问题中的代码段,以下行为符合预期(如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);
}
}
Run Code Online (Sandbox Code Playgroud)
lol.add(list);
是非法的,因为我们可能有a List<List<String>> lol
和a List<Object> list
.实际上,如果我们注释掉有问题的语句,代码就会编译,而这正是我们对第一次调用所做的main
.
问题中的所有probablyIllegal
方法都不违法.它们都完全合法且类型安全.编译器中绝对没有错误.它正在做它应该做的事情.
(这是在答案的第一个修订版中提出的;它是对类型不变量参数的一个有价值的补充.)
5.1.10捕获转换
设G命名一个泛型类型声明,其中n个形式类型参数A 1 ... A n具有相应的边界U 1 ... U n.存在从G <T 1 ... T n >到G <S 1 ... S n >的捕获转换,其中,对于1 <= i <= n:
- 如果T i是表单的通配符类型参数,
?
那么......- 如果T i是形式
? extends
B i的通配符类型参数,则...- 如果T i是形式
? super
B i的通配符类型参数,则...- 否则,S i = T i.
捕获转换不会递归应用.
这一部分可能令人困惑,特别是关于捕获转换的非递归应用(特此是CC),但关键是不是所有的?
CC 都可以; 这取决于它出现在哪里.规则4中没有递归应用,但是当规则2或3适用时,相应的B i本身可能是CC的结果.
让我们通过几个简单的例子来解决:
List<?>
可以CC List<String>
?
第1条规则可以CCList<? extends Number>
可以CC List<Integer>
?
通过第2条CAN CCNumber
List<? extends Number>
可以不 CCList<String>
?
通过第2条,但是编译时错误可能发生CC由于不兼容的类型现在让我们尝试一些嵌套:
List<List<?>>
可以不 CCList<List<String>>
?
可以不 CCList<? extends List<?>>
可以CC List<List<String>>
?
可以通过规则2进行CCList<?>
,它可以是CCList<String>
?
可以CCList<? extends List<? extends Number>>
可以CC List<List<Integer>>
?
可以通过规则2进行CCList<? extends Number>
,它可以是CCList<Integer>
?
可以CCList<? extends List<? extends Number>>
可以不 CCList<List<Integer>>
?
可以通过规则2进行CCList<? extends Number>
,它可以是CC,但在应用时会产生编译时错误List<Integer>
?
可以CC为了进一步说明为什么?
CC和其他人不能这样做,请考虑以下规则:您不能直接实例化通配符类型.也就是说,以下给出了编译时错误:
// WildSnippet1
new HashMap<?,?>(); // DOES NOT COMPILE!!!
new HashMap<List<?>, ?>(); // DOES NOT COMPILE!!!
new HashMap<?, Set<?>>(); // DOES NOT COMPILE!!!
Run Code Online (Sandbox Code Playgroud)
但是,以下编译就好了:
// WildSnippet2
new HashMap<List<?>,Set<?>>(); // compiles fine!
new HashMap<Map<?,?>, Map<?,Map<?,?>>>(); // compiles fine!
Run Code Online (Sandbox Code Playgroud)
WildSnippet2
编译的原因是因为,如上所述,没有一个?
可以CC.在WildSnippet1
,罐头CC中的K
或者V
(或两者)HashMap<K,V>
,这使得通过new
非法直接实例化.
归档时间: |
|
查看次数: |
7197 次 |
最近记录: |