Scala的sealed關鍵字


Scala的sealed關鍵字

緣起

今天在學習Akka的監控策咯過程中看到了下面一段代碼:

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

當時有點好奇,就想去看看Resume, Restart等的實現,於是就看到了下面的代碼:

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到,減少編程的錯誤。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM