简体   繁体   English

不变泛型似乎无法正常工作

[英]Invariant Generics don't seem working correctly

I've read some articles about Covariance , Contravariance , and Invariance in Java, but I'm confused about them.我已经阅读了一些关于 Java 中的CovarianceContravarianceInvariance的文章,但我对它们感到困惑。

I'm using Java 11, and I have a class hierarchy A => B => C (means that C is a subtype of B and A , and B is a subtype of A ) and a class Container :我正在使用Java 11,并且我有一个类层次结构A => B => C (意味着CBA的子类型,而BA的子类型)和类Container

class Container<T> {
    public final T t;
    public Container(T t) {
        this.t = t;
    }
}

for example, if I define a function:例如,如果我定义一个函数:

public Container<B> method(Container<B> param){
  ...
}

here is my confusion, why does the third line compile?这是我的困惑,为什么第三行编译?

method(new Container<>(new A())); // ERROR
method(new Container<>(new B())); // OK
method(new Container<>(new C())); // OK Why ?, I make a correction, this compiles OK

if in Java Generics are invariant .如果在 Java 中泛型是不变的。

When I define something like this:当我定义这样的东西时:

Container<B> conta =  new Container<>(new A()); // ERROR, Its OK!
Container<B> contb =  new Container<>(new B()); // OK, Its OK!
Container<B> contc =  new Container<>(new C()); // Ok, why ? It's not valid, because they are invariant

Covariance is the ability to pass or specify a subtype when a supertype is expetced.协方差是在出现超类型时传递或指定子类型的能力。 If your C class extends B, then C is a child class of B. This relationship between C and B is also called is-a relationship, where an instance of C is also an instance of B. Therefore when your variable contc is expecting a B instance and you're passing new C() , since new C() is an instance of C and C instance is (also)-an instance of B, then the compiler allows the following writing:如果您的 C 类扩展了 B,则 C 是 B 的子类。C 和 B 之间的这种关系也称为is-a关系,其中 C 的实例也是 B 的实例。因此,当您的变量contc期望B 实例并且您正在传递new C() ,因为new C()是 C 的实例并且 C 实例is (also)-an B 的实例,因此编译器允许以下编写:

Container<B> contc = new Container<>(new C());

Conversely, when you're writing相反,当你写作时

Container<B> conta = new Container<>(new A());

you're receiving an error because A is a supertype of B, there is no is-a relationship from A to B, but rather from B to A. This is because every instance of B is also an instance of A, but not every instance of A is an instance of B (To make a silly example, every thumb is a finger but not every finger is a thumb).您收到错误是因为 A 是 B 的超类型,没有从 A 到 B is-a关系,而是从 B 到 A。这是因为 B 的每个实例也是 A 的实例,但不是每个A 的实例是 B 的实例(举一个愚蠢的例子,每个拇指都是手指,但不是每个手指都是拇指)。 A is a generalization of B; A是B的推广; therefore it cannot appear where a B instance is expected.因此它不能出现在预期 B 实例的位置。

Here there's a good article expanding the concept of covariance in java.这里有一篇很好的文章扩展了java中协方差的概念。

https://www.baeldung.com/java-covariant-return-type https://www.baeldung.com/java-covariant-return-type

The question's examples don't demonstrate the invariance of generics.该问题的示例并未证明泛型的不变性。

An example which does demonstrate this would be:一个可以证明这一点的例子是:

ArrayList<Object> ao = new ArrayList<String>(); // does not compile

(You might incorrectly expect the above to compile, because String is a subclass of Object .) (您可能错误地期望上面的代码能够编译,因为StringObject的子类。)

The question shows us different ways to construct Container<B> objects - some of which compile and others which do not, because of the inheritance hierarchy of A , B and C .这个问题向我们展示了构造Container<B>对象的不同方法——由于ABC的继承层次结构,其中一些可以编译,而另一些则不能。

That diamond operator <> means that the created container is of type B in every case.菱形运算符<>意味着创建的容器在​​任何情况下都是B类型。

If you take the following example:如果你看下面的例子:

Container<B> contc =  new Container<>(new C()); // compiles

And re-write it by populating the diamond with C , the you will see that the following does not compile:并通过用C填充菱形来重新编写它,您将看到以下内容无法编译:

Container<B> contc =  new Container<C>(new C()); // does not compile

That will give you the same "incompatible types" compilation error as my ArrayList example.这将为您提供与我的ArrayList示例相同的“不兼容类型”编译错误。

One of the boons introduced with Java 7 is the so-called diamond operator <> . Java 7 引入的好处之一是所谓的菱形运算符<>

And it has been with us for so long, that it's easy to forget that every time when diamond is being used while instantiating a generic class the compiler should infer the generic type from the context.它已经存在了很长时间,以至于很容易忘记,每次在实例化泛型类时使用 diamond 时,编译器都应该从上下文中推断出泛型类型。

If we define a variable which will hold a reference to a list of Person objects like this:如果我们定义一个变量来保存对Person对象列表的引用,如下所示:

List<Person> people = new ArrayList<>(); // effectively - ArrayList<Person>()

the compiler will infer the type of the ArrayList instance from the type of the variable people on the left.编译器将根据左侧变量people的类型推断ArrayList实例的类型。

In the Java language specification , the expression new ArrayList<>() is being described as a class instance creation expression and because it doesn't specify the generic type parameter and is used within a context , it should be classified as being a poly expression .Java 语言规范中,表达式new ArrayList<>()被描述为类实例创建表达式,因为它没有指定泛型类型参数并且在上下文中使用,所以它应该被归类为poly 表达式. A quote from the specification:规范中的引用:

A class instance creation expression is a poly expression (§15.2) if it uses the diamond form for type arguments to the class, and it appears in an assignment context or an invocation context (§5.2, §5.3).如果类实例创建表达式使用菱形形式作为类的类型参数,并且它出现在赋值上下文调用上下文中(第 5.2 节、第 5.3 节),则它是一个多边形表达式(第 15.2 节)。

Ie when diamond <> is used with a generic class instantiation, the actual type will depend on the context in which it appears.即当菱形<>与泛型类实例化一起使用时,实际类型将取决于它出现的上下文

The three statements below represent the case of so-called assignment context .下面的三个语句代表了所谓的赋值上下文的情况。 And all three instances Container will be inferred as being of type B .并且所有三个实例Container都将被推断为B类型。

Container<B> conta = new Container<>(new A()); // 1 - ERROR   because `B t = new A()` is incorrect
Container<B> contb = new Container<>(new B()); // 2 - fine    because `B t = new B()` is correct
Container<B> contc = new Container<>(new C()); // 3 - fine    because `B t = new C()` is also correct

Since all instances of container are of type B and of parameter type expected by the contractor also will be B .由于容器的所有实例都是B类型,并且承包商期望的参数类型也将是B Ie can provide an instance of B or any of its subtypes.即可以提供B或其任何子类型的实例。 Therefore, in the case 1 we are getting a compilation error, meanwhile 2 and 3 ( B and subtype of B ) will compile correctly.因此,在案例1中我们遇到编译错误,同时23BB的子类型)将正确编译。

And it in't a violation of invariant behavior .而且它不违反不变的行为 Think about it this way: we can store in a List<Number> instances of Integer , Byte , Double , etc., that would not lead to any problem since they all can represent their super type Number .这样想:我们可以将IntegerByteDouble等的List<Number>中,这不会导致任何问题,因为它们都可以表示它们的超类型Number But the compiler will not allow assigning this list to any list that is not of type List<Number> because otherwise it would be impossible to ensure that this assignment is safe.但是编译器不允许将此列表分配给任何不是List<Number>类型的列表,因为否则将无法确保此分配是安全的。 And that is what the invariance means - we can assign only List<Number> to a variable of type List<Number> (but we are free to store any subtype of Number in it, it's safe).这就是不变性的含义——我们只能将List<Number>分配给 List<Number List<Number>类型的变量(但我们可以自由地在其中存储Number的任何子类型,这是安全的)。

As an example, let's consider that there's a setter method in the Container class:例如,让我们考虑在Container类中有一个 setter 方法:

public class Container<T> {
    public T t;
    public Container(T t) {
        this.t = t;
    }
        
    public void setT(T t) {
        this.t = t;
    }
}

Now let's use it:现在让我们使用它:

Container<B> contb =  new Container<>(null); // to avoid any confusion initialy `t` will be assigned to `null`

contb.setT(new A()); // compilation error - because expected type is `B` or it's subtype
contb.setT(new B()); // fine
contb.setT(new C()); // fine because C is a subtype of B

When we deal with a class instance creation expression using diamond <> , which is passed to a method as an argument, the type will be inferred from the invocation context as the quote from the specification provided above states.当我们使用 diamond <>处理类实例创建表达式时,该表达式作为参数传递给方法,该类型将从调用上下文中推断为上述规范中的引用状态。

Because method() expects Container<B> , all instances above will be inferred as being of type B .因为method()需要Container<B> ,所以上面的所有实例都将被推断为B类型。

method(new Container<>(new A())); // Error
method(new Container<>(new B())); // OK - because `B t = new B()` is correct
method(new Container<>(new C())); // OK - because `B t = new C()` is also correct

Note笔记

The important thing to mention that prior to Java 8 ( ie with Java 7, because we are using diamond ) the expression new Container<>(new C()) will be interpreted by the compiler as a standalone expression (ie the context will be ignored) creating an instance of Container<C> .值得一提的是,在 Java 8 之前(即 Java 7,因为我们使用的是 diamond ),表达式new Container<>(new C())将被编译器解释为一个独立的表达式(即上下文将是忽略)创建Container<C>的实例。 It means your initial guess was somewhat correct: with Java 7 the below statement would not compile.这意味着您最初的猜测有些正确:对于Java 7 ,以下语句将无法编译。

Container<B> contc = new Container<>(new C()); // Container<B> = Container<C> - is an illegal assignment

But Java 8 has introduced a feature called target types and poly expressions (ie expressions that appear within a context ) that insures that context will always be taken into account by the type inference mechanism.但是 Java 8 引入了一个称为目标类型多表达式(即出现在上下文中的表达式)的特性,以确保类型推断机制始终考虑上下文

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

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