Javaの例外をtry-catchで処理するには?

Scala 3 (Dotty 0.26.0-RC1) 2.13.3 2.12.12
最終更新:2020年5月19日

[AD] scalapediaでは記事作成ボランティアを募集しています

Java言語の例外処理をScalaで実装する方法をご紹介します。

try-catchを使用して例外をキャッチする方法

Scalaは、Java言語と同じように、スローされた例外をキャッチすることができます。
ここでは、Scalaでスローされた例外をキャッチする方法について説明します。

サンプルコードでは、Java言語で書かれた以下のAPIを呼び出すケースを例に説明します。
以下のAPIは、引数で指定した id をキーにデータベースを検索し、該当するUserを返します。
このAPIは、ユーザーが見つからなかった場合は、UserNotFoundExceptionをスローし、データベースでエラーが発生した場合は、DatabaseExceptionをスローします。

java
User searchUser(String id) throws DatabaseException, UserNotFoundException;

Java言語の場合、例外をスローする可能性のあるAPIをtry-catchで囲み、catchのカッコ()内にキャッチする例外の型を指定します。
そして、catchブロック内にエラー処理を記述します。

java
User 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点です。

  1. キャッチする例外の型は、caseで指定する。
    catchブロック内にcase 例外を束縛する変数: 型のように記述すると指定した型に一致する例外をキャッチします。

  2. 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の結果であるUserSomeでくるんで返します。

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では、エラーを表現する方法に、OptionEitherTryを返す方法があります。

ここでは、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を返すことで、成功、失敗を表現できる。

状況に応じて、使い分けるとよいでしょう。

サイト内検索