読者です 読者をやめる 読者になる 読者になる

argius note

プログラミング関連

開発しています



Java SE 7 (3) - 異なる系統の例外をまとめてcatchのあらまし

Java

バイトコードに影響する言語仕様の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