ほげにっき

hogedigoの日記

Java7 try-with-resourcesのsuppressed exceptionsを検証

前エントリでちょこっと書いたJava7 try-with-resourcesのsuppressed exceptionsを検証する。

public class Test implements AutoCloseable{
	
	private int index;
	
	public Test(int index) {
		super();
		this.index = index;
	}

	public static void main(String[] args) throws IOException, SQLException {
		try (Test test = new Test(1);
				Test test2 = new Test(2)) {
			
			test.throwSQLException();
		} catch (IOException e) {
			System.err.println("IOException occurred.");
			throw e;
		} catch (SQLException e) {
			System.err.println("SQLException occurred.");
			throw e;
		}
	}
	
	public void throwSQLException() throws SQLException {
		System.err.println("throwSQLException:"+index);
		throw new SQLException("sql error:"+index);
	}

	@Override
	public void close() throws IOException {
		System.err.println("close:"+index);
		throw new IOException("close error:"+index);		
	}
}

まず面白かったのがAutoCloseable#closeでスローされた例外もtry-with-resourcesのcatchで補足できること。AutoClosebale#closeはthrows Exceptionと定義されているのだが、サブクラスではthrowsをもっと具象的な例外に定義しなおすことが出来る。Java7コンパイラはtryで宣言されている変数の型もちゃんとチェックしていて、closeが投げる例外(上記コードではIOException)が検査例外の場合はcatchするかthrowsしないとコンパイルエラーとなる。


上記コードを実行してみた。

throwSQLException:1
close:2
close:1
SQLException occurred.
Exception in thread "main" java.sql.SQLException: sql error:1
	at Test.throwSQLException(Test.java:30)
	at Test.main(Test.java:18)
	Suppressed: java.io.IOException: close error:2
		at Test.close(Test.java:36)
		at Test.main(Test.java:19)
	Suppressed: java.io.IOException: close error:1
		at Test.close(Test.java:36)
		at Test.main(Test.java:19)
  1. まずthrowSQLExceptionが実行されSQLExceptionがスローされるが、この時点ではまだcatchされない。
  2. 次にTest#closeがtry中宣言の逆順に呼ばれている。それぞれのclose内ではIOExceptionがスローされているが、これらはサプレス(抑制?)されて先にスローされたSQLExceptionに格納されているので、最後までcatchで補足されない。
  3. 最後にSQLExceptionがcatchされる。

スタックトレースを見るとちゃんとサプレスされたIOExceptionが2つ入っているのが分かる。これらはThrowable#getSuppressedメソッドで取得することも出来る。


次にtry{...}中で例外がスローされなかった場合も見てみよう。throwSQLException呼び出しをコメントアウトする。ついでにTestインスタンスを一つ増やした。

public class Test implements AutoCloseable{
	
	private int index;
	
	public Test(int index) {
		super();
		this.index = index;
	}

	public static void main(String[] args) throws IOException, SQLException {
		try (Test test = new Test(1);
				Test test2 = new Test(2);
				Test test3 = new Test(3)) {
			
//			test.throwSQLException();
		} catch (IOException e) {
			System.err.println("IOException occurred.");
			throw e;
		}
	}
	
	public void throwSQLException() throws SQLException {
		System.err.println("throwSQLException:"+index);
		throw new SQLException("sql error:"+index);
	}

	@Override
	public void close() throws IOException {
		System.err.println("close:"+index);
		throw new IOException("close error:"+index);		
	}
}

結果↓

close:3
close:2
close:1
IOException occurred.
Exception in thread "main" java.io.IOException: close error:3
	at Test.close(Test.java:34)
	at Test.main(Test.java:20)
	Suppressed: java.io.IOException: close error:2
		... 2 more
	Suppressed: java.io.IOException: close error:1
		... 2 more

closeが逆順に呼ばれるまでは一緒。最初に発生したIOExceptionがcatchされた。catchされなかった例外は全てcatchされた例外にsuppressed exceptionsとして格納されている。

結論

これはJavaにとっては地味だけど結構革命的な仕様追加ではなかろうか。
ようやくJavaプログラマ積年のモヤモヤが一つ解消された気がする。