filter

特に関数型言語においてはよく filter という名前の関数が用意されている.
これは述語 p とリスト xs を受け取り,xs の要素 x のうち p(x) を満たす要素だけを返すというような関数だ.
これを Java でも書こうとして一苦労したのでその記録を.


まず型を考える.今の Java にはジェネリクスがあるのでそれを利用したかんじになるだろう.
述語を表すインターフェイス

interface Predicate<T> { public boolean apply(T x); }

とかでいいだろう.
リスト型に相当するものは Collection 型でよさそうだ.

public static <T> Collection<T> filter(Predicate<T> p, Collection<T> xs) { ... }

これで xs を破壊的に変更して !p.apply(x) な要素を取り除いてそれを返すように最初は実装した.
しかしこれだと,渡した型は例えば ArrayList なのに,返ってくるのは Collection 型でかなり扱いにくい.
もっといい方法は無いものかと調べていたところに,ちょうど Java Advent Calendar の一環として書かれたこのエントリを見た.
http://d.hatena.ne.jp/t_yano/20101213/1292212666
まさにやりたかったことが書かれていた.
というわけで,型を

public static <T, C extends Object & Collection<T>>
C filter(Predicate<T> p, C xs) { ... }

と変更した.これで渡した型と同じ型が返ってくるようになった.


すると次は xs を破壊的に変更しないようにしたくなる.
単に新たにオブジェクトを作ってそれを返せばいいんでしょ,と思って

public static <T, C extends Object & Collection<T>>
C filter(Predicate<T> p, C xs) {
  C ys = new C();
  ...
  return ys;
}

と書いた.
が,コンパイルエラー.型パラメータに対して new できないらしい.なんと…
Class#newInstance() で生成するといいらしい.2種類くらい例外投げられるけど正直 exit するくらいしか無い気がする.

public static <T, C extends Object & Collection<T>>
C filter(Predicate<T> p, C xs) {
  Class<C> c = xs.getClass();
  try {
    C ys = c.newInstance();
    ...
    return ys;
  } catch (InstantiationException e) {
  ...
}

これも微妙にダメ.Object#getClass() が返すのは Class 型であって Class 型ではないのでコンパイルエラー.
キャストするしかないっぽい.コンパイル時に警告が出るけどどうしようもない.アノテーションで黙らせる.

public static <T, C extends Object & Collection<T>>
C filter(Predicate<T> p, C xs) {
  @SuppressWarnings("unchecked")
  Class<C> c = (Class<C>)xs.getClass();
  try {
    C ys = c.newInstance();
    ...
    return ys;
  } catch (InstantiationException e) {
  ...
}