読者です 読者をやめる 読者になる 読者になる

Java で map や filter

Java

現在の Java にはラムダ式が無いので、map や filter をやろうとすると割とひどいことになります。
例えば、

public interface F<Arg, Ret> {
    Ret _(Arg a);
}

というインターフェイスを用意して、

public class Op {
    public static <T, U> List<U> map(List<T> xs, F<T, U> f) {
        List<U> result = new ArrayList<>();
        for (T x : xs) {
            result.add(f._(x));
        }
        return result;
    }
    public static <T> List<T> filter(List<T> xs, F<T, Boolean> cond) {
        List<T> result = new ArrayList<>();
        for (T x : xs) {
            if (cond._(x))
                result.add(x);
        }
        return result;
    }
}

のように実装すると思います*1
しかし、このようにインターフェイスを使ってしまうと、これを呼び出して使う側にこのようなコードを強制してしまいます。

static List<Integer> allPlus10(List<Integer> xs) {
    return Op.map(xs, new F<Integer, Integer>() {
        public Integer _(Integer x) {
            return x + 10;
        }
    });
}

これは不要な部分ばかりです。

  • new F() { ... }
  • public Integer _(Integer x) { ...}
  • return

これはこれで便利なのですが、毎回毎回インデントが深くなりすぎる上に、要らないコードが多すぎて微妙に読みにくいです。


そこで、もういっそ汎用的な仕組みはあきらめたらいいんじゃないだろうか、ということで、こんなコードでどうでしょうか?

public class Map<T> extends ArrayList<T> { // このクラス名そのままはさすがにないか・・・
    protected void _(T t) { add(t); }
}
public class Filter<T> extends ArrayList<T> {
    protected void _(T t) { add(t); }
}

static List<Integer> allPlus10(final List<Integer> xs) {
    // これをmapの構文と「思い込む」
    return new Map<Integer>() {{ for (int x : xs) _(x + 10); }};
}
static List<Integer> onlyEven(final List<Integer> xs) {
    // これをfilterの構文と「思い込む」
    return new Filter<Integer>() {{ for (int x : xs) if (x % 2 == 0) _(x); }};
}

これだとノイズは

  • for (int x : xs)
  • _(...);

くらいなので、個人的には許容範囲内かなぁ、とか思わなくもないです。
filter は 要素の型と戻り値の型が同じなのに 2 回型を書くことになりますが、それでもインターフェイスでやるよりは少ないです。
map は要素の型と戻り値の型を一回ずつ書けばいいだけなので、インターフェイス版よりもお得感がより強い感じです。


ええっと、もちろん冗談です。
・・・冗談ですよ?
良い子のみんなは真似しないでネ!

*1:自分で cons セルなリストを作って、そのクラスに map メソッドを持たせる、とか、super や extends を頑張るというのもありです。