简体   繁体   English

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

[英]Why is this assignment involving wildcards legal in Java?

Most questions about wildcards want to know why something sensible is rejected by the compiler.大多数关于通配符的问题都想知道为什么编译器会拒绝一些合理的东西。 My question is the opposite.我的问题是相反的。 Why is the following program accepted by the compiler?为什么下面的程序会被编译器接受?

void test(List<? extends Number> g1, List<? extends Number> g2)
{
    g1 = g2;
}

I tried to explain this from the Java Language Specification, but I have not found the answer.我试图从 Java 语言规范中解释这一点,但我还没有找到答案。 I had the impression from various descriptions of Java generics and wildcards that each use of a wildcard is captured as a completely new type, but apparently not here.我从 Java 泛型和通配符的各种描述中得到的印象是,通配符的每次使用都被捕获为一种全新的类型,但显然不是在这里。 I have not found any nasty behavior that follows from this assignment being allowed, but it still seems "wrong".我还没有发现允许此分配后出现任何令人讨厌的行为,但它似乎仍然是“错误的”。

List<? extends Number> List<? extends Number> is best read as: List<? extends Number>最好读为:

This is a list of numbers, but, covariantly.这是一个数字列表,但是,协变。

In other words, this is a list of some concrete but unknown type.换句话说,这是一些具体但未知类型的列表。 However, I do know that, whatever type it might be, at least it is either Number or some subclass thereof.但是,我确实知道,无论它是什么类型,至少它是 Number 或其某个子类。

Generics is weird;泛型很奇怪; once you opt into some variance, you get the restrictions to go along with that.一旦你选择了一些差异,你就会得到相应的限制。 In the case of collections, 'covariance' comes with the baggage of 'no adding'.在集合的情况下,“协方差”带有“不添加”的包袱。

Try it.尝试一下。

g1.add(XXX);

the only thing that is legal for XXX here?这里唯一对XXX合法的事情? null . null That's literally it.字面意思就是这样。 The full and complete and exhaustive list of all you can add to this thing.您可以添加到此内容的所有内容的完整列表。 certainly Number x = 5; g1.add(x);当然Number x = 5; g1.add(x); Number x = 5; g1.add(x); is not going to be allowed by javac here. javac 在这里是不允许的。

By writing List<? extends a thingie>通过编写List<? extends a thingie> List<? extends a thingie> you're saying: Yeah, I want that. List<? extends a thingie>你是说:是的,我想要那个。 I'm signing up to this restriction that I get to add absolutely nothing (other than the academic case of literal null ).我正在签署这个限制,我绝对不会添加任何内容(除了文字null的学术案例)。 In trade for handcuffing yourself, the things you can pass in for g1 is expanded considerably.在给自己上手铐的交易中,您可以为g1传递的东西大大扩展了。

You can also opt into contravariance:您还可以选择逆变:

void foo(List<? super Integer> list) {
    list.add(Integer.valueOf(5)); // works!
    Integer x = list.get(0); // no go
}

contravariance is the opposite.逆变则相反。 add works.添加作品。 get doesn't work.获取不起作用。 Which in this case means: The type of the expression list.get(0) is just.. Object .在这种情况下,这意味着:表达式list.get(0)类型只是.. Object


Now that we've covered that:现在我们已经介绍了:

void test(List<? extends Number> g1, List<? extends Number> g2) {}

means 'my first parameter is a list of numbers, but I opt into covariance handcuffs', and 'my second parameter is a list of numbers, but I also opt into covariance handcuffs for this one too', it now makes sense why java lets you write g1 = g2 .意思是“我的第一个参数是一个数字列表,但我选择使用协方差手铐”,“我的第二个参数是一个数字列表,但我也选择了这个数字的协方差手铐”,现在为什么 java 让你写g1 = g2 g2 is guaranteed to be an X<Y> , where X some concrete subclass of List , and Y is either Number or some subclass thereof. g2 保证是X<Y> ,其中 X 是List某个具体子类,而 Y 是Number或其某个子类。

This is 100% compatible, type-wise, with the notion of 'some sort of list whose type param is some covariant take on Number'.这是 100% 兼容的,类型方面的,具有“某种类型的列表,其类型参数是 Number 的某种协变形式”的概念。 The only thing you can do a List<? extends Number>你唯一能做的就是List<? extends Number> List<? extends Number> is to invoke methods of List where any T in the signatures are 'disabled' for parameters, and replaced by the bound ( Number) for return types. List<? extends Number>是调用 List 的方法,其中签名中的任何 T 对于参数都被“禁用”,并被返回类型的绑定( Number)替换。

That's.. exactly what List<? extends Number>这就是.. List<? extends Number> List<? extends Number> is describing, so it's compatible. List<? extends Number>正在描述,所以它是兼容的。

"I had the impression from various descriptions of Java generics and wildcards that each use of a wildcard is captured as a completely new type, " “我从 Java 泛型和通配符的各种描述中得到的印象是,通配符的每次使用都被捕获为一种全新的类型,”

That statement is correct.这种说法是正确的。

So what?所以呢? You are confusing the type of the object with the type of the variable.您将对象的类型与变量的类型混淆了。

Consider this code:考虑这个代码:

String s = "abc";
Object o = s;

o has type Object which is assignment compatible with the type of s. o 具有类型 Object,它与 s 的类型赋值兼容。 But that doesn't mean String and Object are the same type.但这并不意味着 String 和 Object 是相同的类型。 No different with your example.与你的例子没有什么不同。 You have two different List types for the objects, but one type for the variables.对象有两种不同的 List 类型,但变量有一种类型。 Each variable has type List<?每个变量都有类型 List<? extends Number>, so the assignment is fine.扩展数字>,所以分配没问题。 When you make the assignment, the object's generic type is List<x> for some completely new unknown type x.当你进行赋值时,对象的泛型类型是 List<x> ,用于一些全新的未知类型 x。 But the variable type remains List<?但是变量类型仍然是 List<? extends Number>.扩展数字>。

When I face these questions, I approach this in a slightly different manner.当我面对这些问题时,我的处理方式略有不同。

First of all, every single wildcard is captured , everywhere , by javac .首先,每个wildcard everywherejavac capturedeverywhere In plain english: every time javac "sees" a wildcard it is going to transform that (this is almost accurate as you will see further).简单地说:每次javac “看到” wildcard它都会对其进行转换(这几乎是准确的,您将进一步了解)。 Specifically, let's say we have this:具体来说,假设我们有这个:

List<? extends Number> list;

javac will transform to: javac将转换为:

List<X1> list

where X1 <: Number , where <: means it is a subtype of , as such : X1 is an unknown type that extends Number .其中X1 <: Number ,其中<:表示它是 的子类型,因此: X1 is an unknown type that extends Number This will happen for every single occurrence.这将在每次发生时发生。 And it might be very weird, at first, in some scenarios:在某些情况下,起初可能会很奇怪:

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

}

capture conversion was applied individually to each List , it's like this happened:捕获转换单独应用于每个List ,就像这样发生的:

two(List<X1>, List<X2>)

Now to why is your example accepted, is far more interesting, imho.现在为什么你的例子被接受,更有趣,恕我直言。 You know that capture conversion is applied, but according to the JLS it is not applied everywhere :您知道应用了捕获转换,但根据JLS并未在任何地方应用

If the expression name is a variable that appears "on the left hand side", its type is not subject to capture conversion.如果表达式名称是出现在“左侧”的变量,则其类型不受捕获转换的影响。

It's like saying that only values are capture converted, not variables .这就像说只有被捕获转换,而不是变量

So in this case:所以在这种情况下:

g1 = g2;

g1 has not been capture converted, while g2 has. g1没有被捕获转换,而g2有。 It's like doing:这就像做:

List<? extends Number> g1 = List<X1> (g2) // pseudo-code

We know that X1 <: Number so, as such List<X1> is a subtype of List<? extends Number>我们知道X1 <: Number所以,因此List<X1>List<? extends Number> List<? extends Number> , so the assignment works. List<? extends Number> ,因此分配有效。

Even if you change ? extends Number即使你改变? extends Number ? extends Number to ? ? extends Number? (this is not a bounded wildcard anymore), this would still work. (这不再是有界通配符),这仍然有效。

How could it not be valid?它怎么可能是无效的?

Both variables have identical type (in this case List<? extends Number> ), so the compiler must allow assignment of one to the other.两个变量具有相同的类型(在这种情况下List<? extends Number> ),因此编译器必须允许将一个分配给另一个。


The objects assigned to the variables may have different types, but the variable types are identical, so assignment is always legal.分配给变量的对象可能有不同的类型,但变量类型是相同的,所以赋值总是合法的。

The compiler does not know or care what the actual type of an object assigned to a variable is, even if it can be determined from the code.编译器不知道也不关心分配给变量的对象的实际类型是什么,即使它可以从代码中确定。 It cares only about declared types when checking types.检查类型时,它只关心声明的类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM