Scala的sealed关键字

缘起

今天在学习Akka的监控策咯过程中看到了下面一段代码:

  def supervisorStrategy(): SupervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
    case _: ArithmeticException => Resume
    case _: IllegalArgumentException => Restart
    case _: NullPointerException => Stop
    case _: Exception => Escalate
  }
  

当时有点好奇,就想去看看ResumeRestart等的实现,于是就看到了下面的代码:

object SupervisorStrategy extends SupervisorStrategyLowPriorityImplicits {
sealed trait Directive

/**
 * Resumes message processing for the failed Actor
 */
case object Resume extends Directive

/**
 * Discards the old Actor instance and replaces it with a new,
 * then resumes message processing.
 */
case object Restart extends Directive

/**
 * Stops the Actor
 */
case object Stop extends Directive

/**
 * Escalates the failure to the supervisor of the supervisor,
 * by rethrowing the cause of the failure.
 */
case object Escalate extends Directive

  // ....
}

刚刚Scala不久,不太清楚这里的sealed关键字的作用,本文下面就详细的描述一下这个关键字的作用吧。

模式匹配

模式匹配pattern matching在scala里面是一个重量级的功能,依赖于模式匹配可以优雅地实现很多功能。大致格式如下:

selector match {
  pattern1 => <body1>
  pattern2 => <body2>
  ...
}

pattern总结起来大约以下几类:

  • Wildcard patterns // _ 统配
  • Constant patterns // 常量
  • Variable patterns // 变量
  • Constructor patterns // 构造函数
  • Sequence patterns // 比如List(,). 如果需要匹配剩余的话使用List(0,_*)
  • Tuple patterns // (a,b,c)
  • Typed patterns // 使用类型匹配 case a:Map[,]
  • asInstanceOf[]
  • isInstanceOf[]
  • note(dirlt):这里需要注意容器类型擦除.Array例外因为这个是java内置类型

实际上我们还能够使用pattern完成下面事情:

  • Patterns in variable definitions // val (a,b) = ("123","345");
  • Case sequences as partial functions
    • 直接使用pattern来构造函数.以参数为match对象,在body里面直接编写case.
    • Each case is an entry point to the function, and the parameters are specified with the pattern. The body of each entry point is the right-hand side of the case.
  • Patterns in for expressions // for ((country, city) <- capitals)
    // case sequences as partial function.
    val foo : Option[String] => String = {
      case Some(e) => e
      case None => "???"
    }
    
    val a = Option[String]("hello")
    println(foo(a))
    val b = None
    println(foo(b))

pattern matching过程中还有下面几个问题需要注意:

  • Patterns are tried in the order in which they are written.
  • Variable binding // 有时候我们希望匹配的变量包含外层结构
    • A(1,B(x)) => handle(B(x))
    • A(1, p @ B(_)) => handle(p) # p绑定了B(x)这个匹配
    • A(1, p @ B()) => handle(p) # B是可以包含unapply从type(p) => Boolean的类,做条件判断
  • Pattern guards // 有时候我们希望对pattern做一些限制性条件
    • A(1,e,e) 比如希望后面两个元素相等,但是这个在pm里面没有办法表达
    • A(1,x,y) if x == y => // 通过guard来完成

scala为了方便扩展模式匹配对象的case, 提供case class这个概念。case class和普通class大致相同,不过有以下三个区别,定义上只需要在class之前加上case即可:

  • 提供factory method来方便构造object
  • class parameter隐含val prefix
  • 自带toString,hashCode,equals实现
    case class A(x:Int) {} // implicit val x:Int
    val a = A(1); // factory method.
    println(a.x);
    println(a); // toString = A(1)

case class最大就是可以很方便地用来做pattern matching.

如果我们能够知道某个selector所有可能的pattern的话,那么就能够在编译期做一些安全性检查。但是selector这个过于宽泛,如果将selector限制在类层次上的话,那么还是可以实现的。举例如下:

abstract class A; // sealed abstract class A
case class B(a:Int) extends A;
case class C(a:Int) extends A;
case class D(a:Int) extends A;

val a:A = B(1);

a match {
  case e @ B(_) => println(e)
  case e @ C(_) => println(e)
}

在match a这个过程中,实际上我们可能存在B,C,D三种子类,但是因为我们这里缺少检查。使用sealed关键字可以完成这个工作。sealed class必须和subclass在同一个文件内。A sealed class cannot have any new subclasses added except the ones in the same file. 如果上面增加sealed的话,那么编译会出现如下警告,说明我们没有枚举所有可能的情况。

/Users/dirlt/scala/Hello.scala:8: warning: match may not be exhaustive.
It would fail on the following input: D(_)
a match {
^
one warning found

有三个方式可以解决这个问题,一个是加上对D的处理,一个是使用unchecked annotation, 一个则是在最后用wildcard匹配

    (a : @unchecked)  match {
      case e @ B(_) => println(e)
      case e @ C(_) => println(e)
    }

    a match {
      case e @ B(_) => println(e)
      case e @ C(_) => println(e)
      case _ => throw new RuntimeException("??");
    }
    

sealed

从上面的描述我们可以知道,sealed 关键字主要有2个作用:

  • 其修饰的trait,class只能在当前文件里面被继承
  • 用sealed修饰这样做的目的是告诉scala编译器在检查模式匹配的时候,让scala知道这些case的所有情况,scala就能够在编译的时候进行检查,看你写的代码是否有没有漏掉什么没case到,减少编程的错误。
时间: 2025-01-07 17:28:59

Scala的sealed关键字的相关文章

static类为什么不能用sealed关键字修饰

今天在写代码时,顺手把一个static 类前面加上sealed关键字,结果编译时报错:" 类不能既是静态的又是密封的".很是纳闷,sealed字段修饰类不是代表该类不能被继承吗,于是我去掉sealed关键字,生成一个dll.然后用Reflector查看了这个这个类的IL,结果发现如下情况: public static class Utility 被翻译成下面的代码了 .class public abstract auto ansi sealed beforefieldinit Util

sealed在C#中的作用说明

sealed关键字的作用: 在类声明中使用sealed可防止其它类继承此类:在方法声明中使用sealed修饰符可防止扩充类重写此方法. sealed修饰符主要用于防止非有意的派生,但是它还能促使某些运行时优化.具体说来,由于密封类永远不会有任何派生类,所以对密封类的实例的虚拟函数成员的调用可以转换为非虚拟调用来处理. 密封类: 密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承.如果试图将一个密封类作为其它类的基类,C#将提示出错.理所当然,密封类不能同时又是抽象类,因为抽象

《深入理解Scala》——第1章,第1.3节静态类型和表达力

1.3 静态类型和表达力 深入理解Scala 开发人员中有一个误解,认为静态类型必然导致冗长的代码.之所以如此是因为很多继承自C的语言强制要求程序员必须在代码中多处明确地指定类型.随着软件开发技术和编译器理论的发展,情况已经改变.Scala利用了其中一些技术进步来减少样板(boilerplate)代码,保持代码简洁. Scala做了以下几个简单的设计决策,以提高代码表达力. • 把类型标注(type annotation)换到变量右边. • 类型推断. • 可扩展的语法. • 用户自定义的隐式转

c#中override virtual static abstract sealed 的作用

c#中override virtual static abstract sealed 的作用 说明1: 表示静态的关键字 说明此对象在应用中只存在一份 说明2: C# 是面向对象的程序设计语言,每一个函数都属于一个类. 当一个方法被声明为Static时,这个方法是一个静态方法,编译器会在编译时保留这个方法的实现.也就是说,这个方法属于类,但是不属于任何成员,不管这个类的实例是否存在,它们都会存在.就像入口函数Static void Main,因为它是静态函数,所以可以直接被调用. 当一个方法被声

Scala implicit

Scala implicit implicit基本含义 在Scala中有一个关键字是implicit, 之前一直不知道这个货是干什么的,今天整理了一下. 我们先来看一个例子: def display(input:String):Unit = println(input) 我们可以看到,display函数的定义只是接受String类型的入参,因此调用display("any string")这样的函数是没问题的.但是如果调用display(1)这样的入参不是String类型的话,编译会出

sealed在C#中的作用说明_实用技巧

sealed关键字的作用: 在类声明中使用sealed可防止其它类继承此类:在方法声明中使用sealed修饰符可防止扩充类重写此方法. sealed修饰符主要用于防止非有意的派生,但是它还能促使某些运行时优化.具体说来,由于密封类永远不会有任何派生类,所以对密封类的实例的虚拟函数成员的调用可以转换为非虚拟调用来处理. 密封类: 密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承.如果试图将一个密封类作为其它类的基类,C#将提示出错.理所当然,密封类不能同时又是抽象类,因为抽象

C#网络应用编程基础练习题与答案(四)

编程|网络 1. 与结构化编程方法相比,面向对象编程有哪些优点? [解答] (1) 以过程为中心和对象为中心的比较 结构化编程方法是以过程为中心的,当面对一个问题时,该方法侧重于问题解决过程的层次结构.面向对象的分析和设计方法侧重于对象.对象具有特定的行为和属性,行为和属性决定了对象与其他对象的交互作用方式,以及对象本身的行为方式. (2) 公开数据和隐藏数据的比较 结构化编程方法对数据和过程仅仅进行简单的包装,这些数据和过程是公开的,或者说程序中的其他代码可以访问这些数据和过程.面向对象的实现

C#委托

    函数重载 相同的函数名相同的签名是错误的,但是相同的函数名不同的签名是可行的. 委托 引用存储为函数的类型.生命使用delegate关键字,指定一个函数的签名,包含一个返回类型和参数列表.定义委托后,可以声明该委托类型的变量.初始化该变量使为和该委托有相同函数签名的函数引用使用.就可以使用委托变量来调用这个函数.对该变量的使用就如同使用函数一样.     有了引用函数变量,可以执行其它方式无法完成的操作.:把委托变量作为参数传递给一个函数.则该函数就可以通过委托调用它引用的函数,而且在运

C#实现Singleton的两种方法的比较

比较 Singleton设计模式可以在应用程序创建一个唯一的全局对象,也就是说,这个对象只能被实例化一次. 应用程序中的窗口管理器或者是数据库连接池等,都是Singleton模式的典型应用. 运用C#语言可以很方便地实现Singleton模式,然而同样是实现Singleton模式,由于实现方式的不同,运行效果也会有所不同.下面分别说明并比较C#实现Singleton模式的两种方法: C#特有的方式实现Singleton(方式1) /**//// <summary> /// 单键模式的简单实现方