Naming(toString) Lambda-Expressions for Debugging purpose

一笑奈何 提交于 2019-12-18 09:46:26

问题


Sometimes it is usefull to name lambdas. Especially when you pass them around as parameter.

A realy simple example is

public class Main {
    public static void main(String[] args) {
        Predicate<String> p = nameIt("isNotEmpty", (s) ->  !s.trim().isEmpty());
        maybePrint("Hello", p);
        maybePrint("    ", p);
    }

    static <T> void maybePrint(T s, Predicate<T> pred) {
        if (pred.test(s)) {
            System.out.println(s.toString());
        } else {
            System.err.println(pred + " says no to \"" + s + "\"");
        }
    }
}

It would be nice to have some functionality by the jvm to name lambdas without loosing the great performance optimizations behind the scenes.

Somethink like this would be fine for me:

Predicate<String> p = nameIt("isNotEmpty", (s) -> !s.trim().isEmpty());

回答1:


Here's an alternative that comes to mind:

static <T> Predicate<T> nameIt(String name, Predicate<? super T> pred) {
    return new Predicate<T>() {
        public String toString() { return name; }
        public boolean test(T t) { return pred.test(t); }
    };
}

This seems pretty simple. Although I haven't benchmarked it, it seems like it ought to be pretty fast. It adds a single object and one method call, and it avoids boxing/unboxing overhead.

The drawback is that you have to write a little function like this for every functional interface for which you want to provide named instances.




回答2:


This is my solution(inspired from the solution of andersschuller at https://stackoverflow.com/a/23705160/1325574) for the problem. There maybe some corner cases(Classloading) where this implementation does not work, but for the most simple cases it works.

I have created a small performance test of this with my limited jmh knowledge: https://gist.github.com/picpromusic/4b19c718bec5a652731a65c7720ac5f8

The "Named"-results are measured for the implementation of the answer of @stuartmarks Naming(toString) Lambda-Expressions for Debugging purpose

# Run complete. Total time: 00:40:31

Benchmark                        Mode  Cnt          Score         Error  Units
MyBenchmark.testNamedPredicate  thrpt  200   45938970,625 ±  615390,483  ops/s
MyBenchmark.testPredicate       thrpt  200   23062083,641 ±  154933,675  ops/s
MyBenchmark.testPredicateReal   thrpt  200   48308347,165 ±  395810,356  ops/s
MyBenchmark.testToString        thrpt  200  138366708,182 ± 1177786,195  ops/s
MyBenchmark.testToStringNamed   thrpt  200  252872229,907 ± 8044289,516  ops/s
MyBenchmark.testToStringReal    thrpt  200    6670148,202 ±   40200,984  ops/s

As you can see it is roughly 2 times slower than using an unnamed lambda. So be carefull in setting -DnamedLambdasEnabled=true. Interessting for me is that it is surprisingly expensive to call toString on an Real-lambda. Maybe someone can explain that, or my jmh-test is stupid.

Here is the code:

/**
 * Helper Class to give lambda a name ("toString") for debugging purpose
 *
 */
public class LambdaNamer {

    private static Method TO_STRING;

    static {
        try {
            TO_STRING = Object.class.getMethod("toString");
        } catch (NoSuchMethodException | SecurityException e) {
            throw new RuntimeException("There is something rotten in state of denmark!");
        }
    }

    /**
     * Overrides toString "Method" for a given lambda.
     * 
     * @param name toString result of lambda
     * @param obj the lambda to encapsulate
     * @return the named lambda
     */
    public static <T> T nameIt(String name, T obj) {
        if (Boolean.getBoolean("namedLambdasEnabled")) {
            Class<T> clazz = (Class<T>) obj.getClass();
            Class<?>[] interfaces = clazz.getInterfaces();

            return (T) Proxy.newProxyInstance(//
                    obj.getClass().getClassLoader(),//
                    interfaces, //
                    (Object proxy, Method method, Object[] args) -> {
                        if (TO_STRING.equals(method)) {
                            return name;
                        } else {
                            return method.invoke(obj, args);
                        }
                    });
        } else {
            return obj;
        }
    }
}

Do you have other solutions? Maybe something that does not have performance implications?



来源:https://stackoverflow.com/questions/42876840/namingtostring-lambda-expressions-for-debugging-purpose

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!