什么是密封特性?

时间:2012-06-26 08:15:54

标签: scala sealed traits

密封类在'Scala编程'中描述,但密封特征不是。 我在哪里可以找到有关密封特性的更多信息?

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

6 个答案:

答案 0 :(得分:434)

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

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

答案 1 :(得分:86)

  

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

sealed而言,是的。当然,他们会分享traitclass之间的正常差异。

  

或者,如果没有,有什么区别?

摸拟。

  

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

如果您有sealed class X,那么您必须检查X以及任何子类。 sealed abstract class Xsealed 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。我不是在谈论微妙的原因,而是在面对面的原因,你不能忽视,比如使用类型类。

答案 2 :(得分:52)

来自daily-scala blog

  

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

答案 3 :(得分:27)

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

  

密封修改器适用于课程定义。 密封类可能无法直接继承,除非继承模板在同一源中定义   file作为继承的类。但是,密封类的子类可以在任何地方继承。

     

- M. Odersky. The Scala language specification, version 2.8. online, Sept., 2013.

答案 4 :(得分:3)

‌‌简而言之:

  • 密封特性只能在同一文件中扩展
  • 列出此内容可使编译器轻松知道所有可能的子类型
  • 在可能的亚型数量有限且事先已知的情况下,使用封闭性状
  • 在Java中创建类似枚举的方法
  • 帮助定义代数数据类型(ADT)

以及更多详细信息 Everything about sealed traits in Scala

答案 5 :(得分:0)

还可以密封定义特质,并且只能通过一组固定的case classes进行扩展。 正常性状密封性状之间的核心区别可以概括如下:

  • 普通特征是开放的,因此,只要它们提供以下特征,任何数量的类都可以从该特征继承 所有必需的方法以及这些类的实例可以通过特征的属性互换使用 必需的方法。 正常特征层次结构可轻松添加其他子类:只需定义您的类并 实施必要的方法。但是,这使得添加新方法变得很困难: 需要将方法添加到所有现有的子类中,其中可能有很多。

  • 密封特征:它们仅允许一组固定的类从它们继承,并且所有 必须在同一文件或REPL命令中将继承类与特征本身一起定义。 相反,密封特征层次结构:添加新方法很容易,因为新方法可以 只需对每个子类进行模式匹配,然后确定每个子类要做什么。但是,添加 新的子类很困难,因为您需要转到所有现有的模式匹配并将案例添加到 处理您的新子类。

例如

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的值只能是JSON空,布尔值,数字,字符串,数组或字典。
  • 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