我试图编译以下代码,但最后一行没有编译:
class SuperContainer (
val shapeSets: Set[MyContainer[Shape]] = Set.empty[MyContainer[Shape]]) {
def addAct(el: MyContainer[Shape]) = {
new SuperContainer(shapeSets + el)
}
}
class MyContainer[A](val ls: Set[A] = Set.empty[A]) {
def addElement(el: A) = {
new MyContainer(ls + el)
}
}
abstract class Shape
case class Circle(radius: Int) extends Shape {
override def toString = "Circle(" + radius + ")"
}
case class Square(s: Int) extends Shape {
override def toString = "Square(" + s + ")"
}
object MyContainer {
def main(args: Array[String]) {
//Circle Container
val myc1 = new MyContainer[Circle]()
val myc11 = myc1.addElement(new Circle(6))
//Square Container
val myc2 = new MyContainer[Square]()
val myc21 = myc2.addElement(new Square(6))
val scont = new SuperContainer
scont.addAct(myc11) //does not compile
}
}
Scala编译器建议我在MyContainer类定义中使用+A,但这样做会出现其他编译错误。我是做错了什么,还是这只是Scala的限制?有什么办法可以克服这个问题吗?
为了实现您想要的,MyContainer
必须是协变的:
class MyContainer[+A](val ls: Set[A] = Set.empty[A]) // ...
现在,您对addElement
的定义将导致一个错误,因为A
出现在相反的位置(在本例中作为函数参数)。您必须对您的签名进行如下调整:
def addElement[B >: A](el: B): MyContainer[B]
如果你想一想,这是有道理的:如果你有一个Container[Circle]
(由于协方差,它可以被视为Container[Shape]
),并且你添加了一个Shape
,那么你在结尾有一个Container[Shape]
,而不是一个Container[Circle]
。
addElement
的实施不会改变。
此外,不能使Set[A]
在类之外可用(即,必须删除val
),因为Set[A]
不是协变的。如果要访问元素,则必须添加其他方法来查询集合。
class MyContainer[+A](ls: Set[A] = Set.empty[A]) // ...
更新
这是为了更清楚地解释为什么Set[A]
不能成为MyContainer[+A]
的公共API的一部分。假设我们有:
class A
class B extends A
想象一下:
val x: MyContainer[A] = new MyContainer[B]
由于协方差,我们可以做到这一点。然而,如果我们现在可以调用:
val s = x.ls // get internal set
我们期望s
是Set[A]
类型。然而,x
的内部Set
是而不是Set[A]
的Set[B]
。因此,这不是正确键入的。