Javaでおなじみのtry-catch-finallyを使う方法です。
try {
func()
catch {
case e: IOException => ...
...
finally {
...
}
ただし、以下の点がjavaと異なります。
finallyは値を返しません。
val n = try {
"hoge".toInt
} catch {
case e => 99
} finally {
100
}
n: Int = 99 // 100ではない
Scalaの例外は非チェック例外です。JavaのAPIを呼び出す時にthrowされる例外を全て捕捉しなくてもコンパイルエラーにはなりません。
@throwsアノテーションを用いると、throwsを付与したJavaバイトコードを生成することができます。
Optionは値がない可能性を型で表現しています。
OptionはSome[T]とNoneという派生クラスを持ちます。
Scalaのマップでは、キーを指定してgetした場合、値があればSome(x), なければNoneが返ります。
scala> val map = Map("Japan" -> "Tokyo", "France" -> "Paris")
map: scala.collection.immutable.Map[String,String] = Map(Japan -> Tokyo, France -> Paris)
scala> map.get("France")
res1: Option[String] = Some(Paris)
scala> map.get("America")
res2: Option[String] = None
Optionの分解は次のように行います。
scala> map.get("key") match {
| case Some(x) => x
| case None => "none"
| }
res7: String = none
scala> map.get("key").getOrElse("none")
res9: String = none
nullではなく、Optionを使いましょう。
でも、javaのAPIでnullを返す可能性がある場合はどうしたらよいでしょうか? javaのAPIはOptionを返してくれません。
Optionはabstractなクラスですが、コンパニトンオブジェクトを持っており、そのapplyは次のように実装されています。
def apply[A](x: A): Option[A] = if (x == null) None else Some(x)
scala> Option(null) res2: Option[Null] = None
となるので、Option(someJavaApi()) とすればjavaのAPIが返すnullをOptionで扱うことができます。
ネットワーク、データベース、ファイルシステムなどの外部リソースにアクセスした結果を返す場合のように、エラーを含めた応答を返すときによく使われます。
EitherはRight[T, V]とLeft[T, V]という派生クラスを持ち、Rightには成功、Leftには失敗を入れます。
ファイルにアクセスする関数を定義します。
scala> def file(filename: String): Either[FileNotFoundException, FileInputStream] = {
|
| try {
| Right(new FileInputStream(filename))
| } catch {
| case e: FileNotFoundException => Left(e)
| }
| }
次のような挙動をとります。
scala> file("exist.txt") // 存在するファイル
res1: Either[java.io.FileNotFoundException,java.io.FileInputStream] = Right(java.io.FileInputStream@2145433b)
scala> file("noexist.text") // 存在しないファイル
res2: Either[java.io.FileNotFoundException,java.io.FileInputStream] = Left(java.io.FileNotFoundException: no.text (No such file or directory))
RightかLeftはパターマッチで判定できます。
file(name) match {
case Right(in) => // inを使ってファイル処理
case Left(e) => println(e.toString) // エラー処理
}
scala2.10.0以降で使える機能です。
TryはSuccess[T]とFailure[Throwable]という派生クラスを持ち、成功時にはSuccess、失敗時にはFailureが返ります。
Success, FailureはFutureを用いた非同期処理で利用されています。
Eitherと似ているように感じますが、Tryは処理が成功した場合はSuccess、例外が発生した場合はNonFatal(致命的ではない)な場合はFailureが返ります。
NonFatalも2.10.0で登場した機能です。
scala2.11.7ではscala.util.control.NonFatal.applyは次のように実装されています。
object NonFatal {
def apply(t: Throwable): Boolean = t match {
// VirtualMachineError includes OutOfMemoryError and other fatal errors
case _: VirtualMachineError | _: ThreadDeath | _: InterruptedException | _: LinkageError | _: ControlThrowable => false
case _ => true
}
...
NonFatalではない、つまり致命的だと判断されるのは以下の例外です。
これらの例外が発生した場合は、Failureではなくそのまま例外がスローされます。
NonFatalな例外(Failure)は基本的にアプリケーションでエラー通知する
、握り潰す
、デフォルト値を設定する
などの対応をとるべき例外です。
NonFatalかどうかの判定は次のように行います。
try {
// dangerous stuff
} catch {
case NonFatal(e) => log.error(e, "Something not that bad.")
// or
case e if NonFatal(e) => log.error(e, "Something not that bad.")
}
Tryは内部で、NonFatalかどうかでキャッチするべき例外を区別しています。
例外処理を行う場合は、NonFatalによる判定に合わせておいた方がよさそうです。
Try, Success, Failureは次のように使います。
scala> import scala.util.{Try, Success, Failure}
import scala.util.{Try, Success, Failure}
scala> val result = Try(10/5)
result: scala.util.Try[Int] = Success(2)
scala> val result = Try(10/0)
result: scala.util.Try[Int] = Failure(java.lang.ArithmeticException: / by zero)
scala> Try(1/0) match {
| case Success(v) => println(v)
| case Failure(e) => println(e.toString)
| }
scala> val sum = for {
| int1 <- Try(Integer.parseInt("1"))
| int2 <- Try(Integer.parseInt("2"))
| } yield {
| int1 + int2
| }
sum: scala.util.Try[Int] = Success(3)
scala> val sum = for {
| int1 <- Try(Integer.parseInt("1"))
| int2 <- Try(Integer.parseInt("foo"))
| } yield {
| int1 + int2
| }
sum: scala.util.Try[Int] = Failure(java.lang.NumberFormatException: For input string: "foo")
FailureからSuccessにリカバリすることもできます。
scala> val result = Try(10/0)
result: scala.util.Try[Int] = Failure(java.lang.ArithmeticException: / by zero)
scala> result.recover {
| case e: ArithmeticException => 0 // 0除算してしまった場合は0にする
| }
res11: scala.util.Try[Int] = Success(0)