我最近一直在努力推动对Scala的理解,我无法弄清楚有关协变/逆变类型参数的一些事情。
假设我有一个名为Basket
的课程如下:
class Basket[+A <: Fruit](items: List[A]) {
// ...
def addAll[B >: A <: Fruit](newItems: List[B]): Basket[B] =
new Basket(items ++ newItems)
// ...
}
和这样的一些类:
trait Fruit
class Banana extends Fruit
class Orange extends Fruit
我确信这些断言是正确的:
Basket[Fruit]
可以实例化
Basket[String]
无法实例化(因为String
不是Fruit
的子类型)
Basket[Banana]
是Basket[Fruit]
Basket[Orange]
是Basket[Fruit]
此代码:
val bananaBasket: Basket[Banana] = new Basket(List(new Banana, new Banana))
bananaBasket.addAll(List(new Orange))
将返回Basket[Fruit]
此代码:
val bananaBasket: Basket[Banana] = new Basket(List(new Banana, new Banana))
bananaBasket.addAll(List(new Banana))
将返回Basket[Banana]
我不明白的是B >: A
如何影响方法的返回类型。为什么当我添加Orange
时,返回类型变为Basket[Fruit]
并且当我添加{时{1}},它保持Banana
?它是否寻找“最低”的普通超类型?
答案 0 :(得分:3)
是的,Scala编译器试图找到最低的常见超类型。您可以在Scala中的任何位置查看它,包括标准库类。考虑List的这个例子,它的参数类型也是协变的:
1.0 :: List(1, 2, 3)
// result type is AnyVal, least common ancestor of Double and Int
res1: List[AnyVal] = List(1.0, 1, 2, 3)
"0" :: List(1, 2, 3)
// result type is List[Any], lowest common ancestor of String and Int
res2: List[Any] = List(0, 1, 2, 3)
0 :: List(1, 2, 3)
// result type is List[Int] exactly
res3: List[Int] = List(0, 1, 2, 3)
res2.head
res4: Any = 0
res2.head.asInstanceOf[String]
res5: String = "0"
有人可以说这是Scala类型系统的一个可疑功能,因为它很容易以Any
(在我的例子中它是如何)或{{1}之类的东西结束。 (如果您正在处理案例类),那么错误消息就会产生误导。
如果你想限制Scala概括你的类型,你应该使用&#34;悲伤的帽子&#34;类型约束Product with Serializable
。然后你的代码看起来像这样(为了便于阅读,我将类更改为case类):
<:<
您可以在以下信息丰富的博文中了解有关此模式的更多信息:http://blog.bruchez.name/2015/11/generalized-type-constraints-in-scala.html