Java SE 7 (3) - 異なる系統の例外をまとめてcatchのあらまし
バイトコードに影響する言語仕様の3つ目、"Catching Multiple Exception Types and Rethrowing Exceptions with Improved Type Checking"です。
これは、言語仕様変更としては不可分のものなのでしょう。
ですが、使う側としては別々に見ていった方が分かりやすいと思うので、分けます。
- 複数のExceptionのcatch
- 改善された型チェックによるExceptionの再throw
複数のExceptionのcatch
前者。多重例外catchのほうが直訳っぽい?
異なる系統の例外だけど、ハンドリングは一緒くたにしたい。例外処理を書いていると、そういった場面によく遭遇します。
たとえばこんな感じ。
try { Connection conn = DriverManager.getConnection("..."); try { /* do something*/ } finally { conn.close(); } InputStream is = new FileInputStream("a.txt"); try { /* do something*/ } finally { is.close(); } } catch (IOException e) { log(e); throw e; } catch (SQLException e) { log(e); throw e; } // RuntimeExceptionは捕捉したくないのでcatch(Exception)ではダメ!!
これが、こう書けるようになりました。
try { Connection conn = DriverManager.getConnection("..."); try { /* do something*/ } finally { conn.close(); } InputStream is = new FileInputStream("a.txt"); try { /* do something*/ } finally { is.close(); } } catch (IOException | SQLException e) { log(e); throw e; }
エラーハンドリングが複雑な場合には非常に有用です。
バイトコードレベルではどうなっているのでしょう。
今回は、Java6以前では表現できない形になっています。
import java.io.*; import java.sql.*; final class Main { static void throwIOException() throws IOException { throw new IOException(); } static void throwSQLException() throws SQLException { throw new SQLException(); } public static void f1() throws Exception { try { throwIOException(); throwSQLException(); } catch (IOException e) { e.printStackTrace(); throw e; } catch (SQLException e) { e.printStackTrace(); throw e; } } public static void f2() throws IOException, SQLException { try { throwIOException(); throwSQLException(); } catch (IOException | SQLException e) { e.printStackTrace(); throw e; } } }
これをjavapすると、
Compiled from "3b.java" final class Main { Main(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return static void throwIOException() throws java.io.IOException; Code: 0: new #2 // class java/io/IOException 3: dup 4: invokespecial #3 // Method java/io/IOException."<init>":()V 7: athrow static void throwSQLException() throws java.sql.SQLException; Code: 0: new #4 // class java/sql/SQLException 3: dup 4: invokespecial #5 // Method java/sql/SQLException."<init>":()V 7: athrow public static void f1() throws java.lang.Exception; Code: 0: invokestatic #6 // Method throwIOException:()V 3: invokestatic #7 // Method throwSQLException:()V 6: goto 23 9: astore_0 10: aload_0 11: invokevirtual #8 // Method java/io/IOException.printStackTrace:()V 14: aload_0 15: athrow 16: astore_0 17: aload_0 18: invokevirtual #9 // Method java/sql/SQLException.printStackTrace:()V 21: aload_0 22: athrow 23: return Exception table: from to target type 0 6 9 Class java/io/IOException 0 6 16 Class java/sql/SQLException public static void f2() throws java.io.IOException, java.sql.SQLException; Code: 0: invokestatic #6 // Method throwIOException:()V 3: invokestatic #7 // Method throwSQLException:()V 6: goto 16 9: astore_0 10: aload_0 11: invokevirtual #10 // Method java/lang/Exception.printStackTrace:()V 14: aload_0 15: athrow 16: return Exception table: from to target type 0 6 9 Class java/io/IOException 0 6 9 Class java/sql/SQLException }
となりました。
注目すべきは、Exception table の"target"のところ。
Exception table: from to target type 0 6 9 Class java/io/IOException 0 6 9 Class java/sql/SQLException
このテーブルは大雑把に言うと、from-toの間で例外が発生したらtargetのアドレスにジャンプ、というのを定義している箇所です。
これまではできなかった、異なる系統の例外を同じジャンプ先に指定することができるようになっています。この辺りは、次の「例外型チェックの改善」と併せて実現可能になった機能だと思われます。
改善された型チェックによるExceptionの再throw
後者。
次のコードをJDK6のjavacでコンパイルすると、
import java.io.*; import java.sql.*; final class Main { static void throwIOException() throws IOException { throw new IOException(); } static void throwSQLException() throws SQLException { throw new SQLException(); } public static void f3() throws IOException, SQLException { try { throwIOException(); throwSQLException(); } catch (Exception e) { e.printStackTrace(); throw e; } } }
$ /java1.6/bin/javac 3c.java 3c.java:21: 例外 java.lang.Exception は報告されません。スローするにはキャッチまたは、スロー宣言をしなければなりません。 throw e; ^ エラー 1 個
となってしまいます。
これがJDK7だと、コンパイルエラーになりません。
以前はコンパイラが単純に「再throwされる例外型Exceptionはthrows(のチェックされる例外のリスト)に無いからダメ」としていたのが、「再throwされる例外型Exceptionは、実際はthrowsにあるIOException,SQLExceptionのどちらかだからOK」のように、例外型を細かく判別してくれるようになったんですね。
上のバイトコード(抜粋)
public static void f3() throws java.io.IOException, java.sql.SQLException; Code: 0: invokestatic #6 // Method throwIOException:()V 3: invokestatic #7 // Method throwSQLException:()V 6: goto 16 9: astore_0 10: aload_0 11: invokevirtual #9 // Method java/lang/Exception.printStackTrace:()V 14: aload_0 15: athrow 16: return Exception table: from to target type 0 6 9 Class java/lang/Exception