360

密封类在“Scala 编程”中进行了描述,但密封特性没有。我在哪里可以找到有关密封特性的更多信息?

我想知道,如果密封特征与密封类相同?或者,如果不是,有什么区别?什么时候使用密封特性是个好主意(什么时候不)?

4

6 回答 6

510

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

因此,如果可能的子类型的数量是有限的并且事先已知,则应该使用密封特征(或密封抽象类)。有关更多示例,您可以查看列表选项实现。

于 2012-06-26T08:55:30.240 回答
93

密封特征与密封类相同吗?

就目前sealed而言,是的。trait当然,它们共享和之间的正常差异class

或者,如果不是,有什么区别?

没有实际意义。

什么时候使用密封特性是个好主意(什么时候不)?

如果您有sealed class X,那么您必须检查X任何子类。sealed abstract class X或的情况并非如此sealed trait X。所以你可以这样做sealed abstract class X,但这比仅仅更冗长trait而且几乎没有优势。

使用abstract classover a的主要优点trait是它可以接收参数。在使用类型类时,这一优势尤为重要。例如,假设您要构建一个排序树。你可以这样写:

sealed abstract class Tree[T : Ordering]

但你不能这样做:

sealed trait Tree[T : Ordering]

因为上下文边界(和视图边界)是使用隐式参数实现的。鉴于特征不能接收参数,你不能这样做。

就个人而言,我更喜欢sealed trait并使用它,除非某些特殊原因让我使用sealed abstract class. 而且我不是在谈论微妙的原因,而是您不能忽视的表面原因,例如使用类型类。

于 2012-06-26T16:48:33.107 回答
57

来自Daily-scala 博客

当一个特征被“密封”时,它的所有子类都在同一个文件中声明,这使得子类集是有限的,这允许某些编译器检查。

于 2012-06-26T08:20:15.967 回答
30

我也觉得有必要指出你的规格:

密封修饰符适用于类定义。密封类不能直接继承,除非继承模板与被继承类定义在同一个源文件中。但是,密封类的子类可以在任何地方继承。

M.奥德斯基。Scala 语言规范,2.8 版。在线,2013 年 9 月。

于 2013-09-03T15:03:19.347 回答
9

简要地:

  • 密封特征只能在同一个文件中扩展
  • 列出这个让编译器很容易知道所有可能的子类型
  • 当可能的亚型数量有限且事先已知时,使用密封特征
  • 一种在 Java 中创建类似枚举的方法
  • 帮助定义代数数据类型 (ADT)

以及更多细节 关于 Scala 中的密封特征的一切

于 2018-07-29T18:45:09.120 回答
5

特征也可以被定义为密封的,并且只能通过一组固定的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

一般而言,密封特征适用于对您期望子类数量变化很小或根本不变化的层次结构进行建模。可以使用密封特征建模的一个很好的例子是 JSON.

  • JSON只能是JSONnull、布尔值、数字、字符串、数组或字典。
  • JSON20 年来没有改变,所以不太可能有人需要JSON 用额外的子类来扩展我们的。
  • 虽然子类的集合是固定的,但我们可能想要对JSONblob 执行的操作范围是无限的:解析它、序列化它、漂亮打印它、缩小它、清理它等等。因此建模是有意义的作为封闭密封特征层次结构的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
于 2020-08-05T16:53:43.123 回答