我正在玩scala的类型系统并发现了一个奇怪的案例。我有充分的理由相信,我不理解协变和协方差。
这是我的问题:
我有两个类,Point和ColorPoint,它是Point的子类。
class Point(val x : Int, val y : Int)
class ColorPoint(x : Int, y : Int, val red : Int, val green : Int, val blue : Int) extends Point(x,y)
此类将B转换为A,而B应为A的超类型:
class CoVariance[+A]{
def cast[B >: A](x : B) : A = {
return x.asInstanceOf[A]
}
}
此类将B转换为A,而B应为A的超类型:
class ContraVariance[-A]{
def cast[B, A <: B](x : B) : A = {
return x.asInstanceOf[A]
}
}
案例1:
val co = new CoVariance[Point]
val color_point = new ColorPoint(1,2,3,4,5)
val point_co = co.cast(color_point)
println(point_co.x)
如果我写出来的话:
// Covariance[Point] ->
// cast[B :> Point](x : B) : Point -> (fill in ColorPoint)
// Cast[ColorPoint :> Point] : Point
我希望这是不正确的,因为ColorPoint不是Point的超类型,但scala不会抱怨。
下一个:
val contra = new ContraVariance[Point]
val color_point_contra = new ColorPoint(1,2,3,4,5)
val point_contra = contra.cast(color_point_contra)
println(point_contra.x)
如果我写出来的话:
// ContraVariance[Point] ->
// cast[B, Point <: B](x : B) : Point -> (fill in ColorPoint)
// cast[ColorPoint, Point <: ColorPoint] : Point
我也希望这是不正确的,但scala并没有抱怨。我会说Point不是ColorPoint的子类型。
我的推理是正确的还是我错过了什么?
答案 0 :(得分:5)
我认为你误解了协变和逆变的立场。它并不意味着您能够在某些类型之间强制转换,它只是建立参数化类型之间的继承关系。
您只能将类型参数标记为协变或逆变位置。当您说Container[+A]
时,如果Container[A]
是Container[B]
的子类型,则表示您可以将A
的所有实例视为B
的子类型。这对于不可变容器类是有意义的:您可以将List[Person]
视为List[Employee]
的父级。请注意,这并未说明投射规则 - 这些规则不变。
逆变相似,但相反。如果您有Writer[-A]
,则表示如果Writer[A]
是Writer[B]
的子类型,B
是A
的子类型。您可以看到这也是如此直观的:如果您有一个Writer[Person]
可以将Person写入某个目的地,并且您有Writer[Employee]
作为只能编写Employees的编写器,那么它是有意义的Writer[Employee]
成为Writer[Person]
的父级,因为编写Person
是编写完整Employee
的子任务,即使它与类型本身相反。< / p>
答案 1 :(得分:2)
asInstanceOf[T]
忽略了类型检查。所以你甚至可以进行以下演员:
def cast[B](a:A):B = a.asInstanceOf[B]
适用于任何A
和B
。
因此,在您的情况下,Scala不会抱怨。
如果我理解正确,您只希望在类型处于正确关系(父子)时使用cast
方法。我想,你不需要在课堂声明中+/-
。只有两个不同的演员:
implicit class CastToParent[A](a:A) {
def cast[B >: A]:B = a.asInstanceOf[B]
}
implicit class CastToChild[A](a:A) {
def cast[B <: A]:B = a.asInstanceOf[B]
}
这样您就可以获得所需的转化次数。
trait A
trait B extends A
trait C
val a:A = new B {}
val b = a.cast[B] //parent to child
val a1 = b.cast[A] //child to parent.
val c = a.cast[C] // don't compile