什么是关于案例类继承的问题?

74

在寻找其他内容的过程中,很偶然地我发现了一些有关case class继承是多么恶魔般的评论。有这个叫做ProductN的东西,卑鄙和国王、精灵和巫师,以及在case class继承中会失去某种非常理想的属性。那么case class继承有何问题?

2个回答

120

一个词: equality

case类提供了equalshashCode的默认实现。这个等价关系被称为equals,它遵循以下属性:

  1. 对于所有的xx 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没有按预期工作与模式匹配器的交互


2
在面向对象的范式中,这种不对称等价关系似乎是一件有用的事情,就像在类型层面上,ColoredPointPoint 的子类,但反之则不然。也许需要给它取一个不同于 equals 的名字... 可能是 subEquals - Luigi Plinge
这个例子在2.10.2中无法编译。 - expert
2
一个通用的等于(equals)方法很容易实现,可以满足相等性,将该类作为比较的成员。复制的问题看起来只是一个错误,并且与模式匹配器的交互应该可以正常工作,就像对于非 case 类层次结构一样。 - aepurniet
1
只有在其父类没有覆盖它时,case class 才会获得 equals,因此在这种情况下,ColoredPoint 将使用 Pointequals/hashCode(我不知道这是否已经是2012年的情况),这是对称的(和反射和传递)。你可以认为 ColoredPoint(0, 0, RED) == ColoredPoint(0, 0, GREEN) 是不直观的,我也同意,但问题并不在于 case class 继承:如果 Point 是一个非 case 类覆盖 equals,你将遇到完全相同的问题。copy 更成问题。 - Alexey Romanov
请问有人能够提供最佳实践解决ColoredPoint问题来扩展这个答案吗? - user267817
显示剩余4条评论

-2

那并不是完全正确的。这比谎言更糟糕。

正如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) = ???

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接