首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >不变泛型似乎不能正常工作。

不变泛型似乎不能正常工作。
EN

Stack Overflow用户
提问于 2022-05-16 12:16:52
回答 2查看 150关注 0票数 6

我读过一些关于Java中协方差、反方差和不变性的文章,但我对它们感到困惑。

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

代码语言:javascript
运行
复制
class Container<T> {
    public final T t;
    public Container(T t) {
        this.t = t;
    }
}

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

代码语言:javascript
运行
复制
public Container<B> method(Container<B> param){
  ...
}

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

代码语言:javascript
运行
复制
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中,泛型是不变的。

当我定义这样的东西时:

代码语言:javascript
运行
复制
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
EN

Stack Overflow用户

回答已采纳

发布于 2022-05-17 14:16:46

Java 7带来的好处之一就是所谓的 <>

长期以来,我们很容易忘记,每次在实例化泛型类时,编译器都会从上下文中推断出泛型类型()。

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

代码语言:javascript
运行
复制
List<Person> people = new ArrayList<>(); // effectively - ArrayList<Person>()

编译器将从左边变量ArrayList的类型推断出people实例的类型。

在Java规范中,表达式new ArrayList<>()被描述为一个,因为它没有指定泛型类型参数,并且在上下文中使用,所以它应该被归类为一个PolyExpression。从说明书中引用的一句话:

类实例创建表达式是一个复合表达式 (§15.2),如果它将菱形表单用于类的类型参数,并且它出现在赋值上下文调用上下文中(§5.2,§5.3)。

也就是说,当钻石<>与泛型类实例化一起使用时,实际类型将取决于它出现的上下文

下面的三个语句代表了所谓的赋值上下文。所有三个实例Container都将被推断为B类型。

代码语言:javascript
运行
复制
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的情况下,我们得到一个编译错误,同时23 (BB的子类型)将正确编译。

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

作为一个示例,让我们考虑Container类中有一个setter方法:

代码语言:javascript
运行
复制
public class Container<T> {
    public T t;
    public Container(T t) {
        this.t = t;
    }
        
    public void setT(T t) {
        this.t = t;
    }
}

现在让我们使用它:

代码语言:javascript
运行
复制
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

当我们使用菱形<>处理类实例创建表达式(该表达式作为参数传递给一个方法)时,类型将从调用上下文中推断出来,作为上面提供的规范的引号。

因为method()期望Container<B>,所以上面的所有实例都将被推断为B类型。

代码语言:javascript
运行
复制
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

需要注意的是,在Java 8之前(即使用Java 7,因为我们使用菱形),表达式new Container<>(new C())将被编译器解释为独立的表达式(即上下文将被忽略),从而创建Container<C>的实例。这意味着您最初的猜测在某种程度上是正确的:使用Java 7,下面的语句将不会编译。

代码语言:javascript
运行
复制
Container<B> contc = new Container<>(new C()); // Container<B> = Container<C> - is an illegal assignment

但是Java8引入了一个名为和poly (即出现在上下文中的表达式)的特性,它确保类型推理机制始终会考虑上下文。

票数 2
EN
查看全部 2 条回答
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72259078

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档