sealed
特征只能在与其声明相同的文件中扩展。
它们通常用于提供 enums
的替代方案。由于它们只能在单个文件中扩展,因此编译器知道所有可能的子类型并且可以对其进行推理。
例如声明:
sealed trait Answer
case object Yes extends Answer
case object No extends Answer
如果匹配不完整,编译器将发出警告:
scala> val x: Answer = Yes
x: Answer = Yes
scala> x match {
| case No => println("No")
| }
<console>:12: warning: match is not exhaustive!
missing combination Yes
因此,如果可能的子类型的数量是有限的并且事先已知,则应该使用密封特征(或密封抽象类)。有关更多示例,您可以查看 list 和 option 实现。
密封特征与密封类相同吗?
就 sealed
而言,是的。当然,它们共享 trait
和 class
之间的正常差异。
或者,如果不是,有什么区别?
没有实际意义。
什么时候使用密封特性是个好主意(什么时候不)?
如果您有 sealed class X
,则必须检查 X
以及任何子类。 sealed abstract class X
或 sealed trait X
并非如此。所以你可以做 sealed abstract class X
,但这比 trait
更冗长,而且没有什么优势。
使用 abstract class
而不是 trait
的主要优点是它可以接收参数。在使用类型类时,这一优势尤为重要。例如,假设您要构建一个排序树。你可以这样写:
sealed abstract class Tree[T : Ordering]
但你不能这样做:
sealed trait Tree[T : Ordering]
因为上下文边界(和视图边界)是使用隐式参数实现的。鉴于特征不能接收参数,你不能这样做。
就个人而言,我更喜欢 sealed trait
并使用它,除非某些特殊原因让我使用 sealed abstract class
。而且我不是在谈论微妙的原因,而是您不能忽视的表面原因,例如使用类型类。
[A: F]
) 的工作方式与方差约束不同。相反,它是语法糖,需要在范围内隐含 F[A]
。它通常用于以一种比隐式参数 ((implicit fa: F[A])
) 更简洁且更易于阅读的方式来调用类型类实例,但它在幕后仍然以完全相同的方式工作,正如 Daniel 指出的那样,特征不会去做那件事。
当一个特征被“密封”时,它的所有子类都在同一个文件中声明,这使得子类集是有限的,这允许某些编译器检查。
我也觉得有必要指出你的规格:
密封修饰符适用于类定义。密封类不能直接继承,除非继承模板与被继承类定义在同一个源文件中。但是,密封类的子类可以在任何地方继承。 — M.奥德斯基。 Scala 语言规范,2.8 版。在线,2013 年 9 月。
简要地:
密封特征只能在同一个文件中扩展
列出这个让编译器很容易知道所有可能的子类型
当可能的亚型数量有限且事先已知时,使用密封特征
一种在 Java 中创建类似枚举的方法
帮助定义代数数据类型 (ADT)
并了解更多详情Everything about sealed traits in Scala
特征也可以定义为密封,并且只能通过一组固定的 case classes
进行扩展。 正常特征和密封特征的核心区别可以总结如下:
普通 trait 是开放的,因此任何数量的类都可以从 trait 继承,只要它们提供所有必需的方法,并且这些类的实例可以通过 trait 的必需方法互换使用。一个正常的 trait 层次结构可以很容易地添加额外的子类:只需定义你的类并实现必要的方法。但是,添加新方法变得困难:需要将新方法添加到所有现有子类中,其中可能有很多。
Sealed trait 是封闭的:它们只允许一组固定的类从它们继承,并且所有继承类必须与 trait 本身一起定义在同一个文件或 REPL 命令中。密封的 trait 层次结构则相反:添加新方法很容易,因为新方法可以简单地对每个子类进行模式匹配并决定要为每个子类做什么。但是,添加新的子类很困难,因为您需要转到所有现有的模式匹配并添加案例来处理您的新子类。
举个例子
object SealedTraits extends App{
sealed trait Point
case class Point2D(x: Double, y: Double) extends Point
case class Point3D(x: Double, y: Double, z: Double) extends Point
def hypotenuse(p: Point) = p match {
case Point2D(x, y) => math.sqrt(x x + y y)
case Point3D(x, y, z) => math.sqrt(x x + y y + z z)
}
val points: Array[Point] = Array(Point2D(1, 2), Point3D(4, 5, 6))
for (p <- points) println(hypotenuse(p))
// 2.23606797749979
// 8.774964387392123
通常,密封特征适用于对您期望子类数量变化很小或根本不变化的层次结构进行建模。可以使用 sealed trait 建模的一个很好的例子是 JSON
。
JSON 值只能是 JSON null、布尔值、数字、字符串、数组或字典。
JSON 已经 20 年没有改变,所以不太可能有人需要用额外的子类来扩展我们的 JSON。
虽然子类的集合是固定的,但我们可能想要对 JSON blob 执行的操作范围是无限的:解析它、序列化它、漂亮打印它、缩小它、清理它等等。因此,它是有意义的将 JSON 数据结构建模为封闭的密封特征层次结构,而不是普通的开放特征层次结构。
sealed trait Json
case class Null() extends Json
case class Bool(value: Boolean) extends Json
case class Str(value: String) extends Json
case class Num(value: Double) extends Json
case class Arr(value: Seq[Json]) extends Json
case class Dict(value: Map[String, Json]) extends Json