スポンサーリンク

2016年3月9日水曜日

Android プロジェクトのマルチcatchステートメント

Javaの世界ではJava7は既に過去の遺物で、Java8が主流になってからずいぶん経ちます。Androidアプリの開発環境はこの流れからずいぶん遅れていて、長い間公式にはJDK7でした。最近になってようやくJDK8になったようですが、それでもJDK8の全機能が使える訳ではなく、JDK7の機能でさえ限定的で、使える部分があったり、なかったりという中途半端さです。そんな中途半端な機能の一つにマルチcatch(multi-catch)ステートメントがあります。


マルチcatchステートメントはJDK7で導入された機能です。以下の様な記述方法のことです。
    catch (IOException | SQLException ex) {
        Log.d(TAG, "Error");
        ex.printStackStrace();
    }

これで例外のcatch節がスッキリ記述できると思いました。しかしAndroidプロジェクトで実際に使ってみると、API19以上でないとだめよ、みたいなことを言われてエラーとなってしまいます。API19といえばAndroid4.4。流石に minSdkVersion をここまで上げてしまうにはまだ抵抗があります。なので使うのは諦めていました。

しかしその後色々なサンプルコードを眺めていると、APIレベルが低いものでも普通にマルチcatchを使っていたりするのに気付きました。例えば以下は問題なく使えます。

    try {
        ........    
    } catch (IllegalStateException | IllegalArgumentException ex) {
        ex.printStackTrace();
    }

しかし以下は(API19未満では)エラーとなります。

    try {
        ........    
    } catch (InstantiationException | IllegalAccessException ex) {
        ex.printStackTrace();
    }

違い分かりますか?実は上の例外はどちらもチェックされない例外であり、下の例外はどちらもチェックされる例外であるということです。ではチェックされない例外では使え、チェックされる例外では使えないというこでしょうか?

ところが話はそんな単純ではありません。

下の例に出てくる二つの例外はどちらも ReflectiveOperationException というあまり聞き慣れない例外クラスを親に持ちます。これは何かというと、JDK7で導入されたクラスであり、従来からあるごく一般的な「チェックされる例外」の親クラスとなっています。このクラスを親として持つのは以下の例外です。

  • ClassNotFoundException
  • IllegalAccessException
  • InstantiationException
  • InvocationTargetException
  • NoSuchFieldException
  • NoSuchMethodException

ではこのクラスの目的は何かというと、何かと複雑になりがちなチェックされる例外処理を、このクラス一つでキャッチできるようにすることです。もともとはリフレクション操作で発生する例外を一つにまとめたことからこのような名前になっています。このクラス、Android的にはAPI19から導入されました。

Javaコンパイラはマルチcatchステートメントをコンパイルする時、列挙された例外のできるだけ近い共通の親クラスを探し、まずは例外をその親クラス一つのcatch節で捕捉します。更にその中で instanceof して子供の例外を判定します。

一番目の例の場合、共通の親クラスは RuntimeException であり、これはAPIレベルに関係なく使用できます。しかし二番目の例の場合、共通の親が ReflectiveOperationException であることから、API19以上でしか使用できません。これがエラーになる理由です。

まとめると、catch節に列挙された例外の共通の親クラスが ReflectiveOperationException になる場合、API19以上でしか使用できません。それ意外であればマルチcatchステートメントは使用可能です。

このことを利用するとエラーを回避することができます。例えば以下のように全く関係ない、ReflectiveOperationException を親としない例外(チェックされない例外)を一つ追加して、共通の親をもっと上のレベルに上げてやればいいのです。(この例では SecurityException を追加) これで共通の親は Exception になり、API19未満でも使用できるようになります。関係ない例外を追加するのが気になる場合は、自分で専用のダミー例外クラスを定義してもいいかもしれません。

    try {
        ........    
    } catch (InstantiationException | IllegalAccessException | SecurityException ex) {
        ex.printStackTrace();
    }

0 件のコメント :

コメントを投稿