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

我想知道,一个封闭的特质和一个封闭的职业是否相同? 或者,如果不是,有什么不同? 什么时候使用密封trait是一个好主意(什么时候不是)?


来自daily-scala博客:

当一个trait被“密封”时,它的所有子类都在 同一个文件,这使得子类的集合是有限的 某些编译器检查。


密封trait只能在与其声明相同的文件中扩展。

它们通常用于提供枚举的替代方案。由于它们只能在单个文件中扩展,所以编译器知道每一种可能的子类型,并可以进行推理。

例如声明:

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

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


一个封闭的特质和一个封闭的职业是一样的吗?

就封存而言,是的。当然,他们在性格和阶级上都有正常的差异。

或者,如果不是,有什么不同?

没有实际意义。

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

如果你有一个密封类X,那么你必须检查X以及任何子类。对于封闭的抽象类X或封闭的特征X,情况就不一样了,所以你可以做封闭的抽象类X,但这比trait要啰嗦得多,也没有什么好处。

与trait相比,使用抽象类的主要优点是它可以接收参数。在使用类型类时,这种优势尤其相关。比方说,你想要构建一棵排序树。你可以这样写:

sealed abstract class Tree[T : Ordering]

但你不能这样做:

sealed trait Tree[T : Ordering]

因为上下文边界(和视图边界)是用隐式参数实现的。考虑到trait不能接收参数,你不能这样做。

就我个人而言,我更喜欢使用密封的trait,除非有特定的原因让我使用密封的抽象类。我不是在谈论微妙的原因,而是您不能忽视的直接原因,例如使用类型类。


此外,我觉得有必要向您指出规格:

密封修饰符应用于类定义。密封类不能直接继承,除非继承模板定义在同一个源代码中 文件作为继承的类。但是,密封类的子类可以在任何地方继承。 ——奥德斯基。Scala语言规范,2.8版。2013年9月。


‌‌简要:

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

想了解更多细节 Scala中关于密封特征的一切


trait也可以被定义为密封的,并且只能通过一组固定的case类进行扩展。 正常性状与封闭性状的核心差异可以概括为:

Normal traits are open, so any number of classes can inherit from the trait as long as they provide all the required methods, and instances of those classes can be used interchangeably via the trait's required methods. A normal trait hierarchy makes it easy to add additional sub-classes: just define your class and implement the necessary methods. However, it makes it difficult to add new methods: a new method needs to be added to all existing subclasses, of which there may be many. Sealed traits are closed: they only allow a fixed set of classes to inherit from them, and all inheriting classes must be defined together with the trait itself in the same file or REPL command. A sealed trait hierarchy is the opposite: it is easy to add new methods, since a new method can simply pattern match on each sub-class and decide what it wants to do for each. However, adding new sub-classes is difficult, as you need to go to all existing pattern matches and add the case to handle your new sub-class.

举个例子

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。

A JSON value can only be JSON null, boolean, number, string, array, or dictionary. JSON has not changed in 20 years, so it is unlikely that anyone will need to extend our JSON with additional subclasses. While the set of sub-classes is fixed, the range of operations we may want to do on a JSON blob is unbounded: parse it, serialize it, pretty-print it, minify it, sanitize it, etc. Thus it makes sense to model a JSON data structure as a closed sealed trait hierarchy rather than a normal open trait hierarchy.

  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