Java Generics Hell - ワイルドカード落穂ひろい

Java Generics Hell アドベントカレンダー 15日目。

読者の推奨スキルとしてはOCJP Silverぐらいを想定している。

共変ワイルドカードと反変ワイルドカードについて書いたので、残りの話題を拾っておこう。

Unbounded Wildcard

共変でも反変でもないただのワイルドカードとして<?>を見たことがあるのではないだろうか。
言語仕様上はUnbounded Wildcardと表現される。適当に訳すと無制限ワイルドカード、だろうか。

何が無制限かというと代入が無制限に行える。

List<?> list;
list = new ArrayList<String>();
list = new ArrayList<Integer>();

さて、前回、前々回の内容を把握している方であれば整合性を取るにはどうしたらよいか想像できるのではないだろうか。

  • Listのメソッドの引数のEに対してはnullしか渡せない
  • Listのメソッドの戻り値のEについてはObject型でしか受け取れない

おおよそ、? extendsと? superのツライところ取りみたいな形である。制限はきついが、代入は無制限なので、こうした制限でも構わなければ用いたほうがそのメソッドを利用しやすい。

Javaの標準APIでわかりやすい事例を挙げるとすればjava.util.Collections#disjointだろうか。

public static boolean disjoint(Collection<?> c1, Collection<?> c2)

指定された2つのコレクションに共通の要素が存在しない場合、trueを返す。それぞれのCollection ――これはjava.util.Listの親interfaceにあたる――から要素をObject型で参照できれば済む。

java.util.Collections#reverseは一見するとよい例のように見えるのだが

public static void reverse(List<?> list)

Listの順序を逆にするというものであるが、実は渡したlistを操作して順序を逆転させるのでadd()やset()でnullしか渡せないはずなのにどうしているかというとraw型を用いているということで例としてはあまり適切ではない。

raw型

Java5以降でJavaを学んだ人は基本的にジェネリクスの山括弧<>は省略してはいけない、警告は無視してはいけない、ということを徹底しておくとよい。

しかし、Javaは5以前、つまるところ1.0〜1.4までの間はジェネリクスがなかったわけで、構文上山括弧が用いられていなかった。Java5でそれらの時代のソースコードコンパイルできるように後方互換性として残されたものがraw型である。

List list = new ArrayList();

現代の標準的なコンパイラIDEの設定では警告が出ると思う。分かってて敢えて用いている場合はメソッドにアノテーション@SuppressWarnings("rawtypes")をつけると警告を除去できる。

raw型を用いると基本的に型の安全性が保証されない。明示的にダウンキャストして用いる必要が生じるが、扱いを誤れば実行時に java.lang.ClassCastException が出ることになる。そして、それをコンパイル時点では気づくことができない。

逆に言えば、Java1.4までの時代、java.util.Listなどのコレクションフレームワークを用いたソースコードは常にClassCastExceptionとの戦いであった。現代ではClassCastExceptionに遭遇することは稀であろう。

raw型は歴史的経緯という点が大きいが、変性の制御でどうにもならないときの逃げ道としては時に便利でもある。