如何解决为什么这种涉及通配符的分配在Java中是合法的?
关于通配符的大多数问题都想知道为什么编译器拒绝明智的选择。我的问题是相反的。为什么编译器接受以下程序?
void test(List<? extends Number> g1,List<? extends Number> g2)
{
g1 = g2;
}
我试图从Java语言规范中进行解释,但是我没有找到答案。从Java泛型和通配符的各种描述中,我得到的印象是,通配符的每次使用都被捕获为一种全新的类型,但显然不在这里。我没有发现允许此分配执行任何令人讨厌的行为,但它似乎仍然是“错误的”。
解决方法
List<? extends Number>
最好读为:
这是一个数字列表,但协变量。
换句话说,这是一些具体但未知类型的列表。但是,我确实知道,无论它是什么类型,至少它是Number或其某些子类。
泛型很奇怪;一旦选择了某些差异,您就会受到限制。对于收藏而言,“协方差”与“不增加”的包comes同时出现。
尝试一下。
g1.add(XXX);
这里唯一XXX
合法的东西? null
。就是这样。您可以添加的全部,完整和详尽的清单。肯定Number x = 5; g1.add(x);
不会在这里被javac允许。
写List<? extends a thingie>
就是说:是的,我想要那个。我接受了这一限制,即我绝对不添加任何内容(除了字面null
的学术案例之外)。在为自己戴上手铐的交易中,可以为g1
传递的东西已经大大扩展了。
您还可以选择使用协变:
void foo(List<? super Integer> list) {
list.add(Integer.valueOf(5)); // works!
Integer x = list.get(0); // no go
}
相反是相反的。添加作品。获取不起作用。在这种情况下,这意味着:表达式list.get(0)
的类型为just .. Object
。
现在我们已经涵盖了这一点:
void test(List<? extends Number> g1,List<? extends Number> g2) {}
表示“我的第一个参数是数字列表,但我选择了协方差手铐”,“我的第二个参数也是数字列表,但我也选择了协方差手铐”,这很有意义为什么Java让您编写g1 = g2
。保证g2为X<Y>
,其中X是List
的某些具体子类,而Y是Number
或其某些子类。
这是100%兼容的,在类型上与“某种列表,其类型参数是Number的一些协变量”的概念兼容。唯一可以做List<? extends Number>
的事情就是调用List方法,其中签名中的任何T都被“禁用”以作为参数,并替换为绑定的(Number)
来作为返回类型。
这就是List<? extends Number>
所描述的,因此是兼容的。
“从Java泛型和通配符的各种描述中,我得到的印象是,通配符的每次使用都被捕获为全新的类型,”
那句话是正确的。
那又怎样?您正在将对象的类型与变量的类型混淆。
考虑以下代码:
String s = "abc";
Object o = s;
o具有类型为Object的对象,该对象的分配与s的类型兼容。但这并不意味着String和Object是同一类型。您的示例也是如此。对象有两种不同的列表类型,而变量有一种类型。每个变量的类型为List ,所以分配很好。进行分配时,对于某些全新的未知类型x,对象的通用类型为List 当我面对这些问题时,我会以一种略有不同的方式来处理这个问题。
首先,每个wildcard
都是captured
的{{1}}。用简单的英语来说:每次everywhere
“看到”一个javac
都会改变它(这将几乎准确,您将进一步看到)。具体来说,假设我们有以下内容:
javac
wildcard
将转换为:
List<? extends Number> list;
其中javac
,其中List<X1> list
表示它是的子类型,例如:X1 <: Number
。每次发生都会发生这种情况。在某些情况下,一开始它可能很奇怪:
<:
捕获转换分别应用于每个X1 is an unknown type that extends Number
,就像这样:
public static void main(String[] args) {
List<?> l = new ArrayList<String>();
one(l);
two(l,l); // fails
}
public static <T> void one(List<T> single){
}
public static <T> void two(List<T> left,List<T> right){
}
现在,为什么您的示例被接受了,真是太有趣了,恕我直言。您知道应用了捕获转换,但是根据List
,它是not applied everywhere:
如果表达式名称是显示在“左侧”的变量,则其类型不进行捕获转换。
这就像说,只有值是捕获转换的,而不是变量。
所以在这种情况下:
two(List<X1>,List<X2>)
JLS
尚未捕获转换,而g1 = g2;
已捕获。就像这样做:
g1
我们知道g2
是List<? extends Number> g1 = List<X1> (g2) // pseudo-code
的子类型,因此分配有效。
即使将X1 <: Number
更改为List<X1>
(这不再是有界通配符),它仍然可以工作。
怎么无效有效?
这两个变量的类型相同(在本例中为List<? extends Number>
),因此编译器必须允许将一个分配给另一个。
分配给变量的对象可以具有不同的类型,但是变量类型是相同的,因此分配始终是合法的。
即使可以从代码中确定,编译器也不知道或不在乎分配给变量的对象的 actual 类型是什么。检查类型时,它仅关心声明的类型。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。