Java:検査例外(チェック例外)を非検査例外(非チェック例外)として送出する

長年Javaをやってきたのだが、こんな方法があるとは知らなかった。この発見は驚愕だ。

検査例外をそのまま非検査例外として送出する方法があるのだ

問題

問題としては、検査例外がある場合には、それをcatchして処理するか、上位までそれを宣言しておかねばならない。

例えば、以下のようなメソッドがある場合、

void raiseSome() throws IOException {
  throw new IOException("raised");
}

これを使う方としては、以下として処理してしまうか、

void foo() {
  try {
    raiseSome();
  } catch (IOException ex) {
    ... 何らかの処理
  }
}

あるいは、例外処理をその上位に任せるべく以下にしなければならない。

void bar() throws IOException {
  raiseSome();
}

もし、途中にIOExceptionを宣言していないメソッドがあり、その上にまでIOExceptionをあげたいとすると、RuntimeExceptionでラップしてやる必要がある。例えば、

void foobar() {
  try {
    raiseSome();
  } catch (IOException ex) {
    throw new RuntimeException(ex);
  }
}

ラップの必要なく上にあげる

しかし、この方法を使うと、ラップも必要なく、そのままIOExceptionを上にあげることができるのである。つまりこうだ。

void foobar() { // IOExceptionを宣言してないが、IOExceptionが発生する
   raiseSome(); // IOExceptionに関するひみつの処理を行う
}

実際の方法

実際の方法としては以下だ。

  void foobar() {
    try {
      raiseSome();
    } catch (IOException ex) {
      throwAsUnchecked(ex);
    }
  }

  void raiseSome() throws IOException {
    throw new IOException("raised");
  }

  @SuppressWarnings("unchecked")
  private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E {
    throw (E)exception;
  }

たしかにfoobar()を呼び出すとIOExceptionが発生してくれる。

このthrowAsUncheckedというメソッドにおいて、引数として指定された検査例外を非検査例外として送出しているのである。このメソッドは、LambdaExceptionUtilにあったものだ。

※これについては、Java:Stream/Optionalで検査例外/チェック例外を扱う方法を参照。

このメソッドでは、「throws E」としてThrowableのサブクラスを送出すると宣言しているのだが、コンパイル時には実際の型が決まらないため、コンパイラはこれが検査例外なのか非検査例外なのかわからず、無視してしまうらしい。

こんなテクニックがあったとは知らなんだ。もっと早く教えてくれ。

複数の検査例外のある場合は?

複数の検査例外を定義しているものでも大丈夫だ。例えば以下のようにする。

  void foobar() {
    try {
      raiseIO();
    } catch (Exception ex) {
      throwAsUnchecked(ex);
    }
  }

  void raiseIO() throws InstantiationException, IllegalAccessException {
    Foo.class.newInstance();
  }

  @SuppressWarnings("unchecked")
  private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E {
    throw (E)exception;
  }