为了账号安全,请及时绑定邮箱和手机立即绑定

为什么这个示例不编译,也就是(协、对、内)方差是如何工作的?

为什么这个示例不编译,也就是(协、对、内)方差是如何工作的?

为什么这个示例不编译,也就是(协、对、内)方差是如何工作的?从这个问题,有人能用Scala解释一下以下情况吗?class Slot[+T] (var some: T) {     //  DOES NOT COMPILE     //  "COVARIANT parameter in CONTRAVARIANT position"}我明白+T和T在类型声明中(如果我使用T)。但是,如何编写一个类,该类在其类型参数中是协变的,而不诉诸于创建非参数化?如何确保只能使用T?class Slot[+T] (var some: Object){       def get() = { some.asInstanceOf[T] }}编辑-现在将其归结为以下几点:abstract class _Slot[+T, V <: T] (var some: V) {     def getT() = { some }}这一切都很好,但我现在有两个类型参数,其中我只想要一个。因此,我将再次提出这个问题:我怎么写不变 Slot类,即协变它的类型?编辑2哦!我用var而不是val..以下是我想要的:class Slot[+T] (val some: T) { }
查看完整描述

3 回答

?
慕勒3428872

TA贡献1848条经验 获得超6个赞

一般说来,协变类型参数允许在类是子类型时向下变化(或者,随子类型而变化,因此是“co-”前缀)。更具体而言:

trait List[+A]

List[Int]List[AnyVal]因为IntAnyVal..这意味着您可以提供List[Int]当类型值为List[AnyVal]都是意料之中的。对于泛型来说,这确实是一种非常直观的工作方式,但事实证明,在存在可变数据的情况下使用它是不健全的(破坏了类型系统)。这就是为什么泛型在Java中是不变的。使用Java数组(这些数组是错误的协变量)的不稳定的简单示例:

Object[] arr = new Integer[1];arr[0] = "Hello, there!";

我们刚刚分配了一个类型的值String类型数组Integer[]..出于显而易见的原因,这是个坏消息。Java的类型系统实际上允许在编译时这样做。JVM将“帮助”抛出ArrayStoreException在运行时。Scala的类型系统防止了此问题,因为Array类是不变的(声明是[A]而不是[+A]).

请注意,还有另一种类型的差异称为反向方差..这是非常重要的,因为它解释了为什么协方差会引起一些问题。反方差实际上是协方差的对立面:参数是可变的。向上有亚型。虽然它确实有一个非常重要的应用程序:函数,但它并不常见,部分原因是它违反了直觉。

trait Function1[-P, +R] {
  def apply(p: P): R}

注意“-“对P类型参数这个声明作为一个整体意味着Function1是逆变的P和协变R..因此,我们可以导出以下公理:

T1' <: T1
T2 <: T2'---------------------------------------- S-FunFunction1[T1, T2] <: Function1[T1', T2']

注意T1'的子类型(或同一类型)。T1,而相反的是T2T2'..英文本可阅读如下:

函数A是另一个函数的子类型。B如果参数类型为A的参数类型的超级类型。B的返回类型A的返回类型的子类型。B.

这个规则的原因是留给读者练习的(提示:考虑不同的情况,因为函数是子类型的,就像上面我的数组示例)。

使用您新发现的协方差和反向方差知识,您应该能够了解为什么下面的示例将不编译:

trait List[+A] {
  def cons(hd: A): List[A]}

问题是A是协变的,而cons函数的类型参数为不变量..因此,A改变了错误的方向。有趣的是,我们可以通过List逆变A,但是返回类型List[A]将无效,因为cons函数的返回类型为协变.

我们这里只有两个选择A不变量,丢失协方差的良好、直观的子类型属性,或(B)向cons方法,该方法定义A作为下限:

def cons[B >: A](v: B): List[B]

现在这是有效的。你可以想象A是向下变化的,但是B能够向上变化AA是它的下界。通过这个方法声明,我们可以A是协变的,一切都成功了。

注意,只有当我们返回List,它专门针对不太特定的类型。B..如果你想List可变的,因为你最终试图分配类型的值,事情就会变坏。B类型变量A,这是编译器不允许的。无论何时有可变性,都需要有某种类型的变体,这需要某种类型的方法参数,这意味着(与访问器一起)意味着不变性。协方差适用于不可变数据,因为唯一可能的操作是访问器,访问器可以被赋予协变量返回类型。


查看完整回答
反对 回复 2019-07-05
?
白猪掌柜的

TA贡献1893条经验 获得超10个赞

简单地说,如果允许的话:

  class Slot[+T](var some: T) {
    def get: T = some   
  }

  val slot: Slot[Dog] = new Slot[Dog](new Dog)   
  val slot2: Slot[Animal] = slot  //because of co-variance 
  slot2.some = new Animal   //legal as some is a var
  slot.get ??

slot.get将在运行时抛出一个错误,因为它在转换AnimalDog(哼!)

一般说来,变异与协方差和反向方差不太一致。这就是为什么所有Java集合都是不变的原因。


查看完整回答
反对 回复 2019-07-05
  • 3 回答
  • 0 关注
  • 424 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信