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

【Java】JavaでLINQっぽいことをやる

この前言った通り、結局のところ私が欲しいのは高階関数の機能であり、それ以上のことは望んでいなかったんですが、こんなものを見つけました。

Bringing Closures to Java 5, 6 and 7

恐ろしくシンプルな機構です。callと言うメソッドを持ったインターフェースを無名クラスでOverrideし、適宜呼び出す。それだけです。

サンプルを見ればわかる通り、返り値が必要ない場合はV1,2,3,…<T>インターフェースを、返り値が欲しい時はR1,2,3,…<R, T>インターフェースを作ります。

結局Overrideかよ、と思うかもしれませんが、コード補完を使えば大した手間はかからないし、作りがシンプルなので可読性も意外と悪くないです。

と言うわけで、これを使ってLINQライブラリを作っていきましょう。

Linq用のクラスを作る

Javaには拡張メソッドの類がないので、どうしてもラッパークラスのようなものを作らないといけません。と言うわけで、まずそれを考えてみましょう。

public final class Linq<T> implements Iterable<T> {
    
    private Iterable<T> _iterable;
    
    public Linq(Iterable<T> iterable) {
        _iterable = iterable;
    }
    
    public ArrayList<T> toList() {
        ArrayList<T> list = new ArrayList<T>();
        
        for(T obj : _iterable) {
            list.add(obj);
        }
        
        return list;
    }
    
    @Override
    public Iterator<T> iterator() {
        return _iterable.iterator();
    }
}

こんな感じですかね。ただこうすると、毎回new Linq<T>(iterable)の宣言が必要になってくるんですが、そもそもiterableを渡せばTは自ずと決まるので、なんだか馬鹿馬鹿しいし、実際にテストコードを書いてる時に何度も忘れました。なので、newの代わりにstaticなメソッドインスタンスを返すようにしてみましょう。

public final class Linq<T> implements Iterable<T> {
    
    private Iterable<T> _iterable;
    
    private Linq(Iterable<T> iterable) {
        _iterable = iterable;
    }
    
    public <T> static linq(Iterable<T> iterable) {
        return new Linq<T>(iterable);
    }
    
    public ArrayList<T> toList() {
        ArrayList<T> list = new ArrayList<T>();
        
        for(T obj : _iterable) {
            list.add(obj);
        }
        
        return list;
    }
    
    @Override
    public Iterator<T> iterator() {
        return _iterable.iterator();
    }
}

これで後はこいつがいるパッケージでもstatic importしとけばくだらないジェネリックの宣言にも患わされずに済みます。

whereを実装してみる

順番的にはselectなんでしょうが、簡単なのでwhereを実装します。

public Linq<T> where(R1<Boolean, T> f) {
    List<T> tmp = new ArrayList<T>();
    
    for(T obj : _iterable) {
        if(f.call(obj)) tmp.add(obj);
    }
    
    _iterable = tmp;
    
    return this;
}

やはりLINQを使う以上、メソッドチェーンをキメたいところなので、privateメンバの_iterableのみを書き換え、返り値はそのまま自分自身にします。遅延実行は出来ないですが、まぁ大して欲しいわけでもないので諦めました。

引数となるR1<Boolean, T> conditionは、R1#call()が呼ばれた時にTのオブジェクトを受け取ってBooleanを返すことを示しています。C#のFuncとは引数と返り値の関係が逆になっていますが、まぁ文句は言わないでおきましょう。

これを使って前回と同じことをやってみましょう。

List<String> idList = linq(ids)
            .where(new R1<Boolean, String>() {
                public Boolean call(String args0) {
                    try {
                        Integer.parseInt(args0);
                    } catch(NumberFormatException e) {
                        return false;
                    }
                    
                    return true;
                }
            }
            .toList();

まぁ冗長間は否めないですが、前回よりはマシじゃないですかね。

selectを実装する

public <R> Linq<R> select(R1<R, T> f) {
    List<R> list = new ArrayList<R>();
        
    for(T obj : _collection) {
        list.add(f.call(obj));
    }
    
    return new Linq<R>(list);
    
}

callから返ってくる型がRと確定しているので、特にキャストする必要もありません。

で、前回の。

List<Integer> idList = linq(ids)
            .where(new R1<Boolean, String>() {
                public Boolean call(String args0) {
                    try {
                        Integer.parseInt(args0);
                    } catch(NumberFormatException e) {
                        return false;
                    }
                    
                    return true;
                }
            }
            .select(new R1<Integer, String>() {
                public Integer call(String args0) {
                    return Integer.parseInt(args0);
                }
            }
            .toList();

メソッドチェーンだとインデントが楽でいいですね。

まとめ

後は適当に欲しいメソッドLinq<T>にぶちこんでいくだけです。

ついでにforEach(V1<T>)みたいなメソッドを作っておくと色々捗ります。