![](/img/trans.png)
[英]How to make covariant in scala a java generic method with a parameterized return type
[英]Implementing a method inside a Scala parameterized class with a covariant type
我已经阅读了一些教程,包括关于协变类型的方法签名的主要Scala文档。 假设我有以下抽象类:
abstract class List[+A] {
def head: A
def tail: List[A]
def isEmpty: Boolean
def add[B >: A](element: B): List[B]
protected def printElements: String
override def toString: String = "[" + printElements + "]"
}
我的问题涉及add()
方法的签名。 为什么有必要这样声明? 我们传入的参数是A的超类型。这解决了什么问题? 我试图在直观的层面上理解这一点。
假设我想要制作一个整数列表。 并且假设,为了论证, add
是在没有泛型的情况下实现的。
def add(element: A): List[A]
为了这个例子,让我们假设我们有一些方法来产生一个“空”列表。
def emptyList[A]: List[A] = /* some magic */
现在我想制作整数列表。
(1 to 10).foldRight(emptyList) { (x, acc) => acc.add(x) }
哎呀! 我们出现了问题! 当我调用emptyList
,Scala会推断出最常见的类型 ,并且因为A
是协变的,所以它将假定为Nothing
。 这意味着我只是尝试将一个整数添加到一个没有任何内容的列表中。 我们可以使用显式类型签名来解决此问题,
(1 to 10).foldRight(emptyList[Int]) { (x, acc) => acc.add(x) }
但是,实际上,这并没有解决问题。 它不会增加可读性,只需要用户做额外的工作。 实际上,我应该可以将一个数字附加到一个没有任何内容的列表中。 只是,如果我选择这样做,我就无法将其称为Nothing
列表。 因此,如果我们定义
def add[B >: A](element: B): List[B]
现在,我可以从List[Nothing]
开始,并添加一个Int
。 我得到的东西不再是List[Nothing]
了; 它是List[Int]
,但我可以做到。 如果我接受List[Int]
并稍后再添加一个String
,那么我也可以这样做,但现在我有一个几乎没用的List[Any]
。
特定
abstract class List[+A] {
def add(element: A): List[A]
}
“这个程序没有编译,因为
add
的参数元素是A
类型,我们声明了协变 。这不起作用,因为函数在参数类型中是逆变的 ,在结果类型中是协变的 。为了解决这个问题,我们需要在add
翻转参数元素类型的方差 。
我们通过引入一个新的类型参数B
来实现这一点,该参数B
具有A
作为较低类型的界限 “。
- 参考 。
在此示例中,如果向List add
:
它必须是A
- 在这种情况下, List仍然是List[A]
。
或者它必须是A
任何子类型 - 在这种情况下,元素被上传到A
, List仍然是List[A]
。
或者如果它是另一种类型B
,那么它必须是A
的超类型 - 在这种情况下, List被上传到List[B]
。 (注意:因为Any
只是所有内容的超类型,在最坏的情况下, List将被上传到List[Any]
) 。
当你声明+A
,你会说,例如, List[String]
扩展了List[Object]
。 现在,想象一下:
val ls: List[Object] = List[String]() // Legal because of covariance
ls.add(1) // Adding an int to a list of String?
只有在可以将List的类型扩展为包含任意对象时,这才是合法的,这正是您的添加签名所做的。 否则, add(a: A)
的存在意味着类型系统中的不一致。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.