3

我目前正在学习scala,我对方差注释特别是协方差逆变感到困惑。

所以我做了一些研究,发现了下面的例子

 class Box[+T] {

    def put[U >: T](x: U): List[U] = {
      List(x)
    }

  }

  class Animal {

  }

  class Cat extends Animal {

  }

  class Dog extends Animal {

  }

  var sb: Box[Animal] = new Box[Cat];

所以这表示 Box 类在 T 中是协变的,这意味着 Box[Cat] 是 Box[Animal] 的子类,因为 Cat 是 Animal 的子类。到目前为止我明白这一点。但是当谈到方法参数时,我的理解就结束了。规范说方法参数不能是协变的,所以我们必须使用这个下限注释。

让我们看看方法定义

 def put[U >: T](x: U): List[U] = {
   List(x)
 }

所以 [U >: T] 说 U 必须是 T 的超类

尝试以下代码

  var sb: Box[Animal] = new Box[Cat];
  sb.put(new Cat);

按预期工作,但这让我发疯

  var sb: Box[Animal] = new Box[Cat];
  sb.put(1);

从逻辑上讲,将INT放入 Box of Animals对我来说毫无意义,因为它是正确的,因为INT将被解析为Any,它是Animal的超类。

所以我的问题是

我如何调整put method只接受动物子类型的代码?我不能使用上限注释

class Box[+T] {

    def put[U <: T](x: U): List[U] = {
      List(x)
    }

  }

因为我得到了这个众所周知的错误

协变类型 T 出现在类型中的逆变位置

4

1 回答 1

4

您可以同时添加下限和上限:

class Box[+T] { def put[U >: T <: Animal](x: U): List[U] = List(x) }

但这不是您想要的,因为您连接了 to 的定义,Box并且Animal逻辑上没有理由添加这样的上限。

你说:

从逻辑上讲,将 INT 放入 Box of Animals 中对我来说毫无意义,因为 INT 将被解析为 Animal 的超类 Any。

您不将 aInt放入 aBox[Animal]中,现有框是不可变的(并且不可能使其可变,因为协方差的定义不允许这样做)。相反,您会得到一个新类型的盒子(或在您的put方法的情况下)。如果您的目标是只获得 a List[Anmial],那么您只需指定:

scala> class Box[+T] { def put[U >: T](x: U): List[U] = List(x) }
defined class Box

scala> var b: Box[Animal] = new Box[Cat]
b: Box[Animal] = Box@23041911

scala> val xs: List[Animal] = b put new Dog
xs: List[Animal] = List(Dog@f8d6ec4)

scala> val xs: List[Animal] = b put 1
<console>:14: error: type mismatch;
 found   : Int(1)
 required: Animal
       val xs: List[Animal] = b put 1
                                    ^

scala> val xs = b put 1 // this will result in a List[Any]
xs: List[Any] = List(1)

没有必要使put方法的定义复杂化。

有关为什么需要协变和逆变的更深入解释,请参阅此问题

于 2015-08-05T09:41:03.373 回答