首页 > 解决方案 > Scala中的逆变方法参数

问题描述

这个问题的最佳答案解释了为什么方法参数是逆变的。作者说,如果这会编译:

case class ListNode[+T](h: T, t: ListNode[T]) {
  def head: T = h
  def tail: ListNode[T] = t
  def prepend(elem: T): ListNode[T] =
    ListNode(elem, this)
}

那么这样就可以了:

val list1: ListNode[String] = ListNode("abc", null)
val list2: ListNode[Any] = list1  // list2 and list1 are the same thing
val list3: ListNode[Int] = list2.prepend(1) // def prepend(elem: T): ListNode[T]

请注意,我已删除原始代码段的最后一行。

我的思考过程如下:

  1. 在第 1 行,为ListNode[String]被叫list1分配了一个新的ListNode(someString, null). 这里没有什么奇怪的。list1不是一个ListNode[String]
  2. 在第 2 行,ListNode[Any]分配了 a list1。这很好,因为ListNode它是协变Any的并且是String.
  3. 在第 3 行,调用了 的prepend方法。list2既然list2是 a ListNode[Any]list2.prepend()应该返回 a ListNode[Any]。但是方法调用的结果被分配给了一个ListNode[Int]. 这不可能编译,因为Int它是的子类型Any并且ListNode不是逆变的!

我误解了什么吗?作者怎么能声称这会编译?

标签: scala

解决方案


让我们考虑一下如果我们定义如下会发生什么:

case class ListNode[+T](h: T, t: ListNode[T]) {
  def head: T = h
  def tail: ListNode[T] = t
  def prepend(elem: T): ListNode[T] =
    ListNode(elem, this)
}

如果您查看prepend方法,它将T(ListNode 的类型参数)作为参数并返回ListNode[T],很简单。现在,让我们详细说明一下用法:

val list1: ListNode[String] = ListNode("abc", null)

在上述情况下,String是 的超类型null,并且由于ListNode是已定义的covarient,所以它是正确的。

val list2: ListNode[Any] = list1

在上述第二种情况下,ListNode[String]分配给ListNode[Any]因为Any它的超类型String也是正确的。

val list3: ListNode[Int] = list2.prepend(1)

最后在上述第三种情况下,我们正在做的是预先设置1which is Int。如果您查看方法prepend(elem: T): ListNode[T],我们将Int作为elem值的类型传递并返回ListNode类型T;在这种情况下,ListNode类型为Int。因此,调用返回的值list2.prepend(1)是 type ListNode[Int]。因此,上述执行list3也是可能的(理论上是正确的)。

def prepend(elem: T): ListNode[T]但是,在 scala中,您无法定义covariant类型(您将得到编译错误),因此您将永远无法做到val list3: ListNode[Int] = list2.prepend(1)。但是,您可以使用下限,如下所示:

case class ListNode[+T](h: T, t: ListNode[T]) {
  def head: T = h
  def tail: ListNode[T] = t
  def prepend[U<:T](elem: U): ListNode[U] =
    ListNode[U](elem, this)
}

推荐阅读