Javaでコレクションクロージャメソッドっぽいこと

Rubyのselectとかcollectみたいなものって、Javaでも匿名クラスで一応実現できるけど、それだとどうしても冗長になります。

クロージャの部分にLLの力を借りたらすっきりするかなと思って、Java+Groovy版のコレクションクロージャメソッドを書いてみました。JRuby版とかRhino版も同様に作れると思います。

package test;

import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.Script;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class Groovy {
    private static Map<String, Script> cache = new HashMap<String, Script>();

    private static Object eval(String expr, Binding binding) {
        Script script = cache.get(expr);
        if (script == null) {
            GroovyShell shell = new GroovyShell();
            script = shell.parse(expr);
            cache.put(expr, script);
        }
        script.setBinding(binding);
        return script.run();
    }

    private static Binding createBinding(Collection<?> c) {
        return new Binding(Collections.singletonMap("c", c));
    }

    public static <T, S> Collection<S> collect(Collection<T> c, String expr,
            Class<S> resultClass) {
        return (Collection<S>) eval("c.collect { " + expr + " }",
                createBinding(c));
    }

    public static <T> Collection<T> collect(Collection<T> c, String expr) {
        return (Collection<T>) eval("c.collect { " + expr + " }",
                createBinding(c));
    }

    public static <T> T find(Collection<T> c, String expr) {
        return (T) eval("c.find { " + expr + " }", createBinding(c));
    }

    public static <T> Collection<T> findAll(Collection<T> c, String expr) {
        return (Collection<T>) eval("c.findAll { " + expr + " }",
                createBinding(c));
    }

    public static boolean every(Collection<?> c, String expr) {
        return (Boolean) eval("c.every { " + expr + " }", createBinding(c));
    }

    public static boolean any(Collection<?> c, String expr) {
        return (Boolean) eval("c.any { " + expr + " }", createBinding(c));
    }

    public static <T> T min(Collection<T> c, String expr) {
        return (T) eval("c.min { " + expr + " }", createBinding(c));
    }

    public static <T> T max(Collection<T> c, String expr) {
        return (T) eval("c.max { " + expr + " }", createBinding(c));
    }
}

以下のようにして使います。

package test;

import java.util.Arrays;
import java.util.List;

import static test.Groovy.*;

public class Test {
    public static void main(String[] args) {
        List<String> list = Arrays.asList(
                "C#", "Java", "JavaScript", "Python", "Ruby", "Scala");

        System.out.println("collect : " + collect(list, "it.toUpperCase()"));
        System.out.println("find    : " + find   (list, "it.startsWith('Java')"));
        System.out.println("findAll : " + findAll(list, "it.startsWith('Java')"));
        System.out.println("every   : " + every  (list, "it.matches('[A-Za-z]+')"));
        System.out.println("any     : " + any    (list, "it.contains('#')"));
        System.out.println("min     : " + min    (list, "it.length()"));
        System.out.println("max     : " + max    (list, "it.length()"));
    }
}

実行結果は次のようになります。

collect : [C#, JAVA, JAVASCRIPT, PYTHON, RUBY, SCALA]
find    : Java
findAll : [Java, JavaScript]
every   : false
any     : true
min     : C#
max     : JavaScript

ちょっとした用途になら使えるかも。