[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください
Java言語の例外処理をScalaで実装する方法をご紹介します。
try-catchを使用して例外をキャッチする方法
Scalaは、Java言語と同じように、スローされた例外をキャッチすることができます。
ここでは、Scalaでスローされた例外をキャッチする方法について説明します。
サンプルコードでは、Java言語で書かれた以下のAPIを呼び出すケースを例に説明します。
以下のAPIは、引数で指定した id をキーにデータベースを検索し、該当するUser
を返します。
このAPIは、ユーザーが見つからなかった場合は、UserNotFoundException
をスローし、データベースでエラーが発生した場合は、DatabaseException
をスローします。
javaUser searchUser(String id) throws DatabaseException, UserNotFoundException;
Java言語の場合、例外をスローする可能性のあるAPIをtry-catchで囲み、catch
のカッコ()
内にキャッチする例外の型を指定します。
そして、catch
ブロック内にエラー処理を記述します。
javaUser user = null; try { user = searchUser("00001"); } catch(DatabaseException e) { System.out.print(e.getMessage()); // DatabaseExceptionをキャッチした場合のエラー処理 } catch(UserNotFoundException e) { System.out.print(e.getMessage()); // UserNotFoundExceptionをキャッチした場合のエラー処理 }
それでは、Scalaで例外をキャッチする方法について、説明していきます。
指定した例外をキャッチする方法
Scalaは、Java言語と同様に、例外をスローする処理をtry
で囲んでcatch
で指定した例外をキャッチすることができます。
Java言語と違うところは、以下の2点です。
-
キャッチする例外の型は、
case
で指定する。
catch
ブロック内にcase 例外を束縛する変数: 型
のように記述すると指定した型に一致する例外をキャッチします。 -
try-catch
が、値を返す。
Javaのtry-catch
は文ですが、Scalaのtry-catch
は式です。したがって、値を返します。
以下の例を見てみましょう。
val user: Option[User] = try Some(searchUser("00001")) catch case e: DatabaseException => println(e.getMessage()) None case e: UserNotFoundException => println(e.getMessage()) None user match case Some(user) => println(s"Found user. name=${user.name}") case None => println("Failed to get user.")
この例では、変数user
の値は、次のようになります。
searchUser
の呼び出しが成功した場合には、例外がスローされることはありません。つまりsearchUser
の結果であるUser
をSome
でくるんで返します。
searchUser
の呼び出しが失敗した場合には、例外がスローされます。
catch
ブロック内で例外をキャッチし、エラー処理を行い、最終的にNone
を返します。
すべての例外をキャッチする方法
キャッチする例外の型を指定せずにすべての例外をキャッチする場合は、以下の例のように、case
に例外の型を記述しません。
val user: Option[User] = try Some(searchUser("00001")) catch case e => println(e.getMessage()) None // 例外をキャッチした場合の復帰値 user match case Some(user) => println(s"Found user. name=${user.name}") case None => println("Failed to get user.")
上記のように記述することで、すべての例外をキャッチすることができますが、コンパイル時に以下の警告メッセージが表示されます。
This catches all Throwables. If this is really intended, use `case e : Throwable` to clear this warning. case e => print(e.getMessage())
なぜ、このような警告メッセージが表示されるのでしょうか?
それは、上記のような書き方をした場合には、すべてのThrowable
をキャッチしてしまう、つまり例外だけでなくエラー(Error
)もキャッチしてしまうからです。
致命的なエラー(例えば、OutOfMemoryError
)が発生した場合には、処理を無理に継続するのではなく、アプリケーションを再起動したほうが良いので、コンパイル時に警告メッセージが表示されるのです。
それを承知ですべてのThrowable
をキャッチするには、以下のようにします。
val user: Option[User] = try Some(searchUser("00001")) catch case e: Throwable => println(e.getMessage()) None // 例外をキャッチした場合の復帰値 user match case Some(user) => println(s"Found user. name=${user.name}") case None => println("Failed to get user.")
致命的でない例外をすべてキャッチする方法
では、致命的でない例外をすべてキャッチするにはどうすれば良いでしょうか?
Scalaでは、致命的でない例外をすべてキャッチする方法が用意されています。
以下のように NonFatal
を指定することで、致命的でない例外をすべてキャッチできます。
val user: Option[User] = try Some(searchUser("00001")) catch case NonFatal(e) => println(e.getMessage()) None // 例外をキャッチした場合の復帰値 user match case Some(user) => println(s"Found user. name=${user.name}") case None => println("Failed to get user.")
scala.util.Tryについて
次に例外を処理する別の方法をご紹介します。
例外をスローする方法は、多用すると処理の流れがわかりにくくなる等のデメリットがあります。
Scalaでは、エラーを表現する方法に、Option
、Either
、Try
を返す方法があります。
ここでは、Try
を使用する方法をご紹介します。
以下の例のように、例外をスローするAPIをtry-catch
で囲む代りに、Try
クラスを使用します。
val user = Try(searchUser("00001")) match case Success(user) => Some(user) case Failure(e) => { println(e.getMessage()) None } user match case Some(user) => println(s"Found user. name=${user.name}") case None => println("Failed to get user.")
呼び出し側では、呼び出し結果をパターンマッチで処理します。
- Success - 例外が発生しなかった場合
- Failure - 例外が発生した場合
エラーを処理するその他の方法
Scalaでエラーを処理する方法は、try-catch
, Try
を使用する以外に以下のような方法があります。
- Option
関数が、Option
を返すことで、値の有無を表現できる。
表現できるのは、値の有無だけであり、エラーの種別は表現できない。 - Either
関数がの処理が成功したときは、Right
、処理が失敗したときは、Left
を返すことで、成功、失敗を表現できる。
状況に応じて、使い分けるとよいでしょう。