LoginSignup
33

More than 5 years have passed since last update.

Java 7 以降で導入された、より厳密な型チェックによる「例外再スロー」を理解する

Posted at

はじめに

この記事は、私の別記事でコメント頂いた内容をもとに書きました。@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 でない例をひとつ挙げる。

RethrowTest.java
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 節の宣言の条件を満たさなくなるからである。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33