密封类的主要原因似乎是,这允许编译器在对这些类进行模式匹配时进行彻底搜索。假设我有用于模式匹配的数据类型。玩具示例:
sealed trait Statement
case class Assign(name: String, value: Int) extends Statement
case class Print(name: String) extends Statement
case class IfZero(name: String, thenn: Statement, els: Option[Statement]) extends Statement
case class Block(statements: List[Statement]) extends Statement
这些类的用例是通过模式匹配来使用它们:
def execute(statement: Statement): Unit = statement match {
case Assign(name, value) => ???
case Print(name) => ???
case IfZero(name, thenn, els) => ???
case Block(statements) => statements foreach { execute(_) }
}
为此,Statement
特征为sealed
,以便编译器可以在匹配语句中忘记一种语句时向我发出警告。但案例类怎么样?案例类不能相互继承,但特征和普通类可以。那么,密封案例类也是一种好的做法吗?如果我不这样做可能会出错?
答案 0 :(得分:6)
您不必密封案例类,但您应将其标记为final
,因此禁止任何进一步的继承关系。使它们密封只有在你想要对其子类进行穷举检查时才有用,这不是一个非常可能的用例。
默认情况下将所有类标记为final
是一件好事,因为它禁止API的用户在覆盖其方法时更改这些类的行为。如果你没有专门设计你的类被子类化,那么子类化可能会导致你的应用程序中出现错误,因为子类化的类不再按照它的意图去做了。
答案 1 :(得分:1)
这个答案留给程序员 并非所有时候都可以为默认类定义合理的行为。
这些类型问题的替代方法是使用密封类(公共基类)。
如果我们无法概括默认值,我们可以使用密封类 案件。
在使用案例类时,不需要默认,因为我们涵盖了所有可能性。如果您错过任何一个,我们将收到编译警告match may not be exhaustive
。在密封类中,如果不存在案例,那么我们得到“scala.MatchError:”。
sealed
案例类层次结构。由于必须在同一文件中声明整个层次结构,因此修改现有代码,重置它(使用它的其他代码)并重新部署它可能代价高昂。答案 2 :(得分:0)
我偶尔发现扩展案例类以启用特定实例子集的专门处理非常有用:
case class Ellipse(major: Int, minor: Int) {
def draw() = //general drawing code
}
class Circle(radius: Int) extends Ellipse(radius, radius) {
override def draw() = //faster specialized code for drawing circles
}
我们也可以将它们用于模式匹配:
def draw(e: Ellipse) = {
case c: Circle => //fast path for drawing a circle
case _ => //general case - note that this case must be able to handle
//ellipses that happen to be circular
}
只要子类符合LSP,这是合法的。
这种事情有多重要?可能不是很好。在您自己的应用程序代码中,“默认”密封所有案例类可能很好,因为您始终可以“解封”它们。在图书馆中,如果您的某个用户想要执行上述操作,我会错误地保留未密封的内容。