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