なんか、情報だけチラチラ見かけていて気になっていたので。まあ、半分くらいFutureの時に使っているのですが。
Try
Futureの結果として使っていた、SuccessとFailureの親クラスです。Successが成功、Failureが失敗を表すわけですが、Failureは例外を情報として持ちます。それ以外はSuccessですと。
Futureの時は、計算結果がSuccessまたはFailureとして得られましたが、これを単独で使う場合にはTryコンパニオンオブジェクトのapplyメソッドを使用します。
println(Try { 10 }) // => Success(10) println(Try { throw new Exception("Oops!") }) // => Failure(java.lang.Exception: Oops!)
Try.applyのシグネチャは
def apply[T](r: ⇒ T): Try[T]
なので、引数は一応関数が渡せますよ、と。結果は、処理が成功すればSuccessが、例外がスローされた場合は、NonFatalなもの以外はFailureが返り、NonFatalなものはそのままスローされます。NonFatalなものというのは、また後で…。
使い方はFutureみたいな感じで、map、flatMap、foreachなどを備えているので、for式で使えますし(filterはありますけど、withFilterはないみたいですが…)、recover、recoverWithもあるので、Failureからの回復もOKです。
その他、getやgetOrElseもあるので…なんとなくプログラミングスタイルが見えてくる感じですね。
コード例をちょこちょこと貼っておきます。
import scala.util.{Failure, Success, Try} println(Try { 10 }) // => Success(10) println(Try { throw new Exception("Oops!") }) // => Failure(java.lang.Exception: Oops!) println(Try { 10 }.isSuccess) // => true println(Try { 10 }.isFailure) // => false println(Try { throw new Exception("Oops!") }.isSuccess) // => false println(Try { throw new Exception("Oops!") }.isFailure) // => true println(Try { 10 }.get) // => 10 try { Try { throw new Exception("Oops!") }.get } catch { case e: Exception => println(e) // => java.lang.Exception: Oops! } println(Try { 10 }.getOrElse(50)) // => 10 println(Try { throw new Exception("Oops!") }.getOrElse(50)) // => 50 Try { 10 } foreach println // => 10 Try { throw new Exception("Oops!") } foreach println // => 何も出力されない Try { 10 } map { _ * 2 } foreach println // => 20 Try[Int] { throw new Exception("Oops!") } map { _ * 2 } foreach println // => 何も出力されない println(Try { 40 } recover { case th => 20 }) // => Success(40) println(Try[Int] { throw new NullPointerException("Oops!") } .recover { case e: NullPointerException => 50 }) // => Success(50) println(Try[Int] { throw new NullPointerException("Oops!") } .recover { case e: IllegalArgumentException => 50 }) // => Failure(java.lang.NullPointerException: Oops!) println(Try { 40 } recoverWith { case th => Success(20) }) // => Success(40) println(Try { 40 } recoverWith { case th => Failure(new Exception("Failure?")) }) // => Success(40) println(Try[Int] { throw new NullPointerException("Oops!") } .recoverWith { case e: NullPointerException => Success(50) }) // => Success(50) println(Try[Int] { throw new NullPointerException("Oops!") } .recoverWith { case e: IllegalArgumentException => Success(50) }) // => Failure(java.lang.NullPointerException: Oops!) println(Try[Int] { throw new NullPointerException("Oops!") } .recoverWith { case e: NullPointerException => Failure(new Exception("Failure?")) }) // => Failure(java.lang.Exception: Failure?) println(Try[Int] { throw new NullPointerException("Oops!") } .recoverWith { case e: IllegalArgumentException => Failure(new Exception("Failure?")) }) // => Failure(java.lang.NullPointerException: Oops!) for { n1 <- Try { 10 } n2 <- Try { 20 } } println(n1 + n2) // => 30 for { n1 <- Try { 10 } n2 <- Try[Int] { throw new Exception("Oops!") } } println(n1 + n2) // => 何も出力されない Try { 10 } map { _ * 20 } match { case Success(n) => println(s"Success? => $n") // => Success? => 200 case Failure(e) => println(s"Failure? => $e") } Try[Int] { throw new Exception("Oops!") } map { _ * 20 } match { case Success(n) => println(s"Success? => $n") case Failure(e) => println(s"Failure? => $e") // => Failure? => java.lang.Exception: Oops! }
NonFatal
Try.applyでNonFatalなもの以外は、と書きましたが、NonFatalは致命的なエラーかそうでないかを判定するオブジェクトです。NonFatalなので、致命的なエラーでない例外の場合に真となります。
致命的なエラーとは、
- VirtualMachineErrorのサブクラス(例えばOutOfMemoryError、ただしStackOverflowErrorは除く)
- ThreadDeath
- LinkageError
- InterruptedException
- NotImplementedError
と定義されています。
この他、scala.util.control.ControlThrowableもNonFatalではないと判定されますが、これはScalaでフロー制御を行うための例外で、これまでキャッチされては困るのでNonFatalではないと扱われているようです。
それ以外は、パターンマッチでNonFatalの結果がtrueとなります。
import scala.util.control.NonFatal try { throw new OutOfMemoryError("dummy") } catch { case NonFatal(e) => println(s"Non Fatal [$e]") case th: Throwable => println(s"Cached! [$th]") // => Cached! [java.lang.OutOfMemoryError: dummy] } try { throw new NullPointerException("dummy") } catch { case NonFatal(e) => println(s"Non Fatal [$e]") // => Non Fatal [java.lang.NullPointerException: dummy] case th: Throwable => println(s"Cached! [$th]") } } }
OutOfMemoryErrorはNonFatalとは判定されていませんが、NullPointerExceptionはNonFatalであると判定されています。
Scala 2.10.0からはcatch節で
} catch { case th => ... }
みたいにパターンマッチをかける時に例外の型を書かないと(Throwableとして捕らえるつもりであっても)警告されるようになったので、Scalaでは活用した方がいいんでしょうね。
ところで、NonFatal.apply、unapplyの定義ってこうなっているのですが
def apply(t: Throwable): Boolean = t match { case _: StackOverflowError => true // StackOverflowError ok even though it is a VirtualMachineError // VirtualMachineError includes OutOfMemoryError and other fatal errors case _: VirtualMachineError | _: ThreadDeath | _: InterruptedException | _: LinkageError | _: ControlThrowable | _: NotImplementedError => false case _ => true } /** * Returns Some(t) if NonFatal(t) == true, otherwise None */ def unapply(t: Throwable): Option[Throwable] = if (apply(t)) Some(t) else None
パターンマッチで、こんなのORの書き方もあったんですね…。