scala - 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 行,为
ListNode[String]
被叫list1
分配了一个新的ListNode(someString, null)
. 这里没有什么奇怪的。list1
不是一个ListNode[String]
。 - 在第 2 行,
ListNode[Any]
分配了 alist1
。这很好,因为ListNode
它是协变Any
的并且是String
. - 在第 3 行,调用了 的
prepend
方法。list2
既然list2
是 aListNode[Any]
,list2.prepend()
应该返回 aListNode[Any]
。但是方法调用的结果被分配给了一个ListNode[Int]
. 这不可能编译,因为Int
它是的子类型Any
并且ListNode
不是逆变的!
我误解了什么吗?作者怎么能声称这会编译?
解决方案
让我们考虑一下如果我们定义如下会发生什么:
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)
最后在上述第三种情况下,我们正在做的是预先设置1
which 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)
}
推荐阅读
- vb.net - vb.net Linq 语句使用模糊搜索查找重复项
- qt - 如何在 QCombobox 中设置当前值
- javascript - React.js 数组 .push() 采用多个值
- c# - C# - 如何检查第一个时间段是否在第二个时间段内?
- list - 修改行背景颜色列表 SwiftUI
- java - 读取位置可能由用户在 Sonar 中指定的文件
- docker - Dockerfile:从父镜像复制文件
- c++ - 如何调用通过 constexpr 表达式返回 void 的 constexpr 函数?
- azure - 在 Azure 权限 SDK for Java 中无法通过 GUID 获取实体
- r - R:变量是指值