为什么这个涉及通配符的赋值在 Java 中是合法的?

Ala*_*der 7 java generics types wildcard bounded-wildcard

大多数关于通配符的问题都想知道为什么编译器会拒绝一些合理的东西。我的问题是相反的。为什么下面的程序会被编译器接受?

void test(List<? extends Number> g1, List<? extends Number> g2)
{
    g1 = g2;
}
Run Code Online (Sandbox Code Playgroud)

我试图从 Java 语言规范中解释这一点,但我还没有找到答案。我从 Java 泛型和通配符的各种描述中得到的印象是,通配符的每次使用都被捕获为一种全新的类型,但显然不是在这里。我没有发现允许此分配后出现任何令人讨厌的行为,但它似乎仍然是“错误的”。

Eug*_*ene 4

当我面对这些问题时,我的处理方式略有不同。

首先,每一个单曲wildcard都是captured, everywhere, by javac. 用简单的英语来说:每次javac“看到” a时wildcard,它都会改变它(这几乎是准确的,您将进一步看到)。具体来说,假设我们有这个:

List<? extends Number> list;
Run Code Online (Sandbox Code Playgroud)

javac将转变为:

List<X1> list
Run Code Online (Sandbox Code Playgroud)

其中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){

}
Run Code Online (Sandbox Code Playgroud)

捕获转换分别应用于每个List,就像这样发生的:

two(List<X1>, List<X2>)
Run Code Online (Sandbox Code Playgroud)

现在为什么你的例子被接受,这更有趣,恕我直言。您知道应用了捕获转换,但根据JLS它并没有在任何地方应用

如果表达式名称是出现在“左侧”的变量,则其类型不受捕获转换的影响。

这就像说只有被捕获转换,而不是变量

所以在这种情况下:

g1 = g2;
Run Code Online (Sandbox Code Playgroud)

g1尚未进行捕获转换,而已g2进行。这就像做:

List<? extends Number> g1 = List<X1> (g2) // pseudo-code
Run Code Online (Sandbox Code Playgroud)

我们知道X1 <: Numberso,因为 soList<X1>是 的子类型List<? extends Number>,所以赋值有效。

即使您更改? extends Number?(这不再是有界通配符),这仍然有效。