如果我们已经有类型边界,为什么我们需要变化?

时间:2017-07-12 22:19:52

标签: scala types covariance contravariance

如果我写Foo[_ <: Bar]Foo[+T <: Bar]后者让我做什么,那我对前者无法做到?

是否只是方便,所以可以写def bar: T而不是def bar: Bar

在哪种情况下有用?

说java中没有差异实际上是否准确?是否可以使用<? extends Foo><? super Bar>对其进行建模?

2 个答案:

答案 0 :(得分:2)

类型边界控制哪些类型是有效参数。 协方差/逆变量注释控制具有不同参数的实例之间的子/超类型关系。 Foo[+T]表示如果Foo[A]Foo[B]的子类型,则AB的子类型。 Foo[-T]意味着相反。

class Foo1[T <: Bar]()
class Foo2[+T <: Bar]()
trait Bar
trait Baz extends Bar

val f1: Foo1[Bar] = new Foo1[Baz]() // compile error
val f2: Foo2[Bar] = new Foo2[Baz]() // works just fine

与使用Foo1[_ <: Bar]等类型边界相反的一个好处是编译器将强制类本身的某些属性。例如,这不会编译:

class Foo[+T]() {
  def f(t: T): Unit = {}
}

这也不会:

class Foo[-T]() {
  def f(): T = { ??? }
}

据我所知,Java没有办法明确表示协方差或逆变。这导致了很多错误,尤其是数组是隐式协变的,即使它们不应该是可变的。

答案 1 :(得分:1)

  

说java中没有差异实际上是否准确?是否可以使用<? extends Foo><? super Bar>对其进行建模?

Java通常被认为具有使用站点差异,而不是Scala声明站点差异(嗯,实际上Scala支持两者)。它实际上更具表现力,严格来说:你可以写出更明智的节目,例如:不将任何内容放入List的方法可能是协变的,而放置但不看内容的方法可能是逆变的。使用声明站点差异,您需要具有单独的不可变和可变类型。

问题的一个众所周知的症状是Scala中Set#contains的签名,它不能在不强制A不变的情况下接受Set

仅使用 使用站点差异的问题是,如果您想要保持一致,它会使使用该类型的所有方法的签名复杂化:不仅仅是在类型本身上声明的那些,而是那些称呼它们的人。

另请参阅How does Java's use-site variance compare to C#'s declaration site variance?https://kotlinlang.org/docs/reference/generics.html#variance