Java Generics Hell アドベントカレンダー 18日目。
- 前回(16日目) 型変数のバインド
読者の推奨スキルとしてはOCJP Silverぐらいを想定している。
throws E
Java のジェネリクスの型変数は例外のthrows宣言でも用いることができる。型変数の宣言時にthrow可能な型であることを型変数の境界で示す必要がある。
public <E extends Exception> void hoge() throws E {}
上記はメソッドスコープの型変数 E を extends Exception としてみた。メソッドスコープの型変数だとthrowsに型変数を用いる意味があまりないが、型システムの挙動を見る分にはコード量が少なくて済むので都合が良い。
バインドの仕方でthrowされうる例外が変わる、例外が変わるのでcatchするべき例外も変わる。
// IOExceptionをバインド try { this.<IOException>hoge(); } catch (IOException e) { // 略 } // SQLExceptionをバインド try { this.<SQLException>hoge(); } catch (SQLException e) { // 略 } // RuntimeExceptionをバインド // catch不要 this.<RuntimeException>hoge();
面白いのはRuntimeExceptionをバインドした場合は、catch不要となることだ。
AutoCloseableの例
ここで、java.lang.AutoCloseableを見てみよう。通常、メソッドのthrowsは具象型が用いられる。
public interface AutoCloseable { void close() throws Exception; }
例えばjava.io.ByteArrayInputStreamでは
public class ByteArrayInputStream extends InputStream { // 略 public void close() throws IOException { } }
となっていて、ByteArrayInputStreamの具象型を扱っていてもclose()を呼ぶ際にIOExceptionが発生する扱いとなる。
byte[] buf = new byte[100]; try (ByteArrayInputStream bais = new ByteArrayInputStream(buf)) { // 略 } catch (IOException e) { // close() が throws IOException のため }
throws Exceptionではなくthrows IOExceptionなのは、例外がメソッドからの出力であるため、より狭い、より具体的な型に絞っても良いからである。このあたりはジェネリクス以前からのJavaの型システムの機能性だが、ジェネリクスのワイルドカードの話にも似ている。
というか、オーバーライドしてthrowsなくしておけばよかったじゃないか、という話もあるんだが、APIの歴史的な後の祭り。Java 1.0 のときに thrwos IOExceptionとしてしまったので、これをなくすとthrowされないIOEexceptionをcatchするコードがコンパイルエラーになってしまうので残されているのである。Javaは到達不能コードをコンパイルエラーにする方針なのでしょうがない。
話がそれた。
ここで言っておきたいのは、AutoCloseable型変数に一度代入してしまえば、そのclose()はthrows Exceptionだということだ。
byte[] buf = new byte[100]; ByteArrayInputStream bais = new ByteArrayInputStream(buf); try (AutoCloseable ac = bais) { // 略 } catch (Exception e) { // IOException ではなく Exception になる }
この点がジェネリクスなしでの不都合ということになる。
例外をインスタンス型変数にした場合
ではここにAutoCloseableとの対比としてinterface Foo を以下のように定めてみよう。
public interface Foo<E extends Exception> { void foo() throws E; }
これをimplementsするclassは以下のようになる。
public class Bar implements Foo<IOException> { @Override public void foo() throws IOException {} }
オーバーライドするにあたって throws IOException となった。
これでパラメタライズドタイプを用いて以下のように書ける。
Bar bar = new Bar(); Foo<IOException> foo = bar; try { foo.foo(); } catch (IOException e) { // IOExceptionで済む }
とはいえ、Fooがパラメタライズドタイプになって鬱陶しい。
なお、Foo<RuntimeException>であればcatch不要となる。
まとめ
例外のthrows宣言に型変数を用いることができるが、正直、あんまり活用できるポイントはない。