##はじめに
この記事は、私の別記事でコメント頂いた内容をもとに書きました。@tag1216 さん、コメントありがとうございました。
この記事は、Java SE Documentation で述べられている内容を、別のサンプルコードを用いてよりわかりやすく示しま(したつもりで)す。
##例外再スローとは
文字通り、catch ブロックで捕捉した例外を再度 try-catch 節の外側にスローすることである。
try {
return new java.io.File("./miracle.txt").getCanonicalPath();
} catch (IOException e) {
throw e; // re-throw e
}
例外をメソッド内で re-throw しても誰も catch しない場合は、その例外はそのメソッドの throws 節で宣言されなければならない(以下、非検査例外については議論しない)。
public String getMiracle() throws java.io.IOException {
try {
return new java.io.File("./miracle.txt").getCanonicalPath();
} catch (IOException e) {
throw e; // re-throw e
}
}
##Java 7 以降で導入された例外再スロー
Java 7 以降では、以下の書き方が ok になったのであった。
public String getMiracle() throws java.io.IOException {
try {
return new java.io.File("./miracle.txt").getCanonicalPath();
} catch (Exception e) { // !?
throw e; // re-throw e
}
}
最初にこのコードを見たとき、throws 節が保証しているのは java.io.IOException のスローだけなのにもかかわらず、再スローでは(任意の)Exception をスローしているので、コンパイルエラーになるのではと思った。
Java 7 以降では、コンパイラが try 節内で throw されうる例外の型を厳密に調べるようになったので、上のコード内の Exception e のインスタンス e は実際は java.io.IOException (かそのサブクラス)しか来ないことをコンパイラは知っている。なのでこの書き方でも問題ない、というわけである。
この新しい書き方が可能な条件の詳細は Java SE Documentation(再掲) をご覧頂くとして、以下では再スローする例外の見た目が java.lang.Exception でない例をひとつ挙げる。
public class RethrowTest {
@SuppressWarnings("serial")
public static class Base extends Exception {}
@SuppressWarnings("serial")
public static class Extension extends Base {}
private void throwExtension() throws Extension {}
private void throwBase() throws Base {}
public void rethrowOK() throws Extension {
try {
throwExtension();
} catch (Base e) {
throw e; // ok because this e is guaranteed to be an instance of Extension.
}
}
public void rethrowNG() throws Extension {
try {
throwBase();
} catch (Base e) {
throw e; // Compilation error: Unhandled exception type RethrowTest.Base
}
}
}
ふたつの public メソッド rethrowOK と rethrowNG がある。前者はコンパイルできて、後者はコンパイルエラーになる。
rethrowOK では、try ブロック内で呼ばれる throwExtension メソッドが Extension (とそのサブクラス)のみをスローすることをコンパイラが理解できるので、Extension のスーパークラスである Base を catch ブロックで再スローすることが問題ないわけである。言い換えると、そのスローする「もの」は、見かけ上は Base であるが実は常に Extension のインスタンスであることが保証されているため、rethrowOK の throws 節の宣言と矛盾しない。
rethrowOK 内の catch (Base e) を catch (Exception e) に変更しても、同じ理由で問題なくコンパイルできる(web上で目にする記述のほとんどが後者のように見受けられる)。
一方、rethrowNG は再スローのコード throw e; の箇所でコンパイルエラーになる。try ブロック内で呼ばれる throwBase は Base をスローすることがわかっているため、再スローすると rethrowNG の throws 節の宣言の条件を満たさなくなるからである。