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
もっといい方法は無いものかと調べていたところに,ちょうど 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
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) { ... }