Case 类继承有什么错?

在寻找其他东西的时候,纯属巧合,我偶然发现了一些关于大小写类继承是多么可怕的注释。有一种叫做 ProductN的东西,可怜虫和国王,精灵和巫师,以及某种非常理想的财产是如何随着案例类继承而丢失的。那么 case 类继承有什么错呢?

14170 次浏览

一个词: 平等

case类提供了 equalshashCode的实现。这个等价关系称为 equals,其工作原理如下(即必须具有以下属性) :

  1. 对于所有的 x; x equals xtrue(反身)
  2. 对于 xyz; 如果 x equals yy equals z,那么 x equals z(传递性)
  3. 对于 xy; 如果 x equals y,则 y equals x(对称)

一旦在继承层次结构中允许相等,就可以破坏2和3。下面的例子显示了这一点:

case class Point(x: Int, y: Int)
case class ColoredPoint(x: Int, y: Int, c: Color) extends Point(x, y)

那么我们有:

Point(0, 0) equals ColoredPoint(0, 0, RED)

但是 没有

ColoredPoint(0, 0, RED) equals Point(0, 0)

您可能会争辩说,所有的类层次结构都可能存在这个问题,这是事实。但是 case 类的存在是为了从开发人员的角度简化相等性(还有其他原因) ,因此让它们表现为 不是凭直觉将是自己目标的定义!


还有其他原因; 值得注意的是,copy没有像预期的那样工作与模式匹配器的交互

不全是这样,这比撒谎还糟糕。

正如 Aepurniet在任何情况下提到的那样,类的后继类限制了定义区域,因此必须重新定义相等,因为模式匹配必须与相等完全一致(如果尝试匹配 Point作为 ColoredPoint,那么它将不匹配,因为 color不存在)。

理解如何实现案例类层次结构的相等性。

case class Point(x: Int, y: Int)
case class ColoredPoint(x: Int, y: Int, c: Color) extends Point(x, y)


Point(0, 0) equals ColoredPoint(0, 0, RED)  // false
Point(0, 0) equals ColoredPoint(0, 0, null) // true


ColoredPoint(0, 0, RED) equals Point(0, 0)  // false
ColoredPoint(0, 0, null) equals Point(0, 0) // true

最终可以满足相等关系的要求,即使是对于 case 类的继承者(不重写相等)。

case class ColoredPoint(x: Int, y: Int, c: String)
class RedPoint(x: Int, y: Int) extends ColoredPoint(x, y, "red")
class GreenPoint(x: Int, y: Int) extends ColoredPoint(x, y, "green")


val colored = ColoredPoint(0, 0, "red")
val red1 = new RedPoint(0, 0)
val red2 = new RedPoint(0, 0)
val green = new GreenPoint(0, 0)


red1 equals colored // true
red2 equals colored // true
red1 equals red2 // true


colored equals green // false
red1 equals green // false
red2 equals green // false


def foo(p: GreenPoint) = ???