繁体   English   中英

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

[英]Invariant Generics don't seem working correctly

我已经阅读了一些关于 Java 中的CovarianceContravarianceInvariance的文章,但我对它们感到困惑。

我正在使用Java 11,并且我有一个类层次结构A => B => C (意味着CBA的子类型,而BA的子类型)和类Container

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

例如,如果我定义一个函数:

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

这是我的困惑,为什么第三行编译?

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

如果在 Java 中泛型是不变的。

当我定义这样的东西时:

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

协方差是在出现超类型时传递或指定子类型的能力。 如果您的 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());

相反,当你写作时

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

您收到错误是因为 A 是 B 的超类型,没有从 A 到 B is-a关系,而是从 B 到 A。这是因为 B 的每个实例也是 A 的实例,但不是每个A 的实例是 B 的实例(举一个愚蠢的例子,每个拇指都是手指,但不是每个手指都是拇指)。 A是B的推广; 因此它不能出现在预期 B 实例的位置。

这里有一篇很好的文章扩展了java中协方差的概念。

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

该问题的示例并未证明泛型的不变性。

一个可以证明这一点的例子是:

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

(您可能错误地期望上面的代码能够编译,因为StringObject的子类。)

这个问题向我们展示了构造Container<B>对象的不同方法——由于ABC的继承层次结构,其中一些可以编译,而另一些则不能。

菱形运算符<>意味着创建的容器在​​任何情况下都是B类型。

如果你看下面的例子:

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

并通过用C填充菱形来重新编写它,您将看到以下内容无法编译:

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

这将为您提供与我的ArrayList示例相同的“不兼容类型”编译错误。

Java 7 引入的好处之一是所谓的菱形运算符<>

它已经存在了很长时间,以至于很容易忘记,每次在实例化泛型类时使用 diamond 时,编译器都应该从上下文中推断出泛型类型。

如果我们定义一个变量来保存对Person对象列表的引用,如下所示:

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

编译器将根据左侧变量people的类型推断ArrayList实例的类型。

Java 语言规范中,表达式new ArrayList<>()被描述为类实例创建表达式,因为它没有指定泛型类型参数并且在上下文中使用,所以它应该被归类为poly 表达式. 规范中的引用:

如果类实例创建表达式使用菱形形式作为类的类型参数,并且它出现在赋值上下文调用上下文中(第 5.2 节、第 5.3 节),则它是一个多边形表达式(第 15.2 节)。

即当菱形<>与泛型类实例化一起使用时,实际类型将取决于它出现的上下文

下面的三个语句代表了所谓的赋值上下文的情况。 并且所有三个实例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

由于容器的所有实例都是B类型,并且承包商期望的参数类型也将是B 即可以提供B或其任何子类型的实例。 因此,在案例1中我们遇到编译错误,同时23BB的子类型)将正确编译。

而且它不违反不变的行为 这样想:我们可以将IntegerByteDouble等的List<Number>中,这不会导致任何问题,因为它们都可以表示它们的超类型Number 但是编译器不允许将此列表分配给任何不是List<Number>类型的列表,因为否则将无法确保此分配是安全的。 这就是不变性的含义——我们只能将List<Number>分配给 List<Number List<Number>类型的变量(但我们可以自由地在其中存储Number的任何子类型,这是安全的)。

例如,让我们考虑在Container类中有一个 setter 方法:

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

现在让我们使用它:

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

当我们使用 diamond <>处理类实例创建表达式时,该表达式作为参数传递给方法,该类型将从调用上下文中推断为上述规范中的引用状态。

因为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

笔记

值得一提的是,在 Java 8 之前(即 Java 7,因为我们使用的是 diamond ),表达式new Container<>(new C())将被编译器解释为一个独立的表达式(即上下文将是忽略)创建Container<C>的实例。 这意味着您最初的猜测有些正确:对于Java 7 ,以下语句将无法编译。

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

但是 Java 8 引入了一个称为目标类型多表达式(即出现在上下文中的表达式)的特性,以确保类型推断机制始终考虑上下文

暂无
暂无

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

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