【问题标题】:Naming(toString) Lambda-Expressions for Debugging purpose用于调试目的的命名(toString)Lambda 表达式
【发布时间】:2017-03-18 16:22:32
【问题描述】:

有时命名 lambdas 很有用。特别是当您将它们作为参数传递时。

一个非常简单的例子是

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 + "\"");
        }
    }
}

如果 jvm 有一些功能来命名 lambdas 而不会失去幕后的出色性能优化,那就太好了。

这样的想法对我来说很好:

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

【问题讨论】:

  • 方法引用类似于“命名”lambda
  • 基于方法引用的 lambda 在运行时没有可调试的字符串。既不是 toString 结果,也不是调试器(至少在 Eclipse 中)视图。
  • 你是对的,但现在我了解到你有时可以得到它,请参阅[如何获取 Java 8 方法参考的 MethodInfo? ](stackoverflow.com/questions/19845213/…)。我让它正常工作,有兴趣吗?
  • 我真的很喜欢这个想法:benjiweber.co.uk/blog/2013/12/28/…。我认为它只适用于非静态方法引用,您需要能够使用运行时字节码生成库。

标签: java debugging lambda jvm tostring


【解决方案1】:

我想到了一个替代方案:

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); }
    };
}

这看起来很简单。虽然我没有对它进行基准测试,但它似乎应该很快。它添加了一个对象和一个方法调用,并且避免了装箱/拆箱开销。

缺点是您必须为要为其提供命名实例的每个功能接口编写一个像这样的小函数。

【讨论】:

  • 是的,就像stackoverflow.com/questions/23704355/… 的回答一样,这是一种快速但不适用于每个 lambda 的解决方案。我将更新我的答案,并在其中包含此答案的基准测试结果。
【解决方案2】:

这是我的解决方案(灵感来自 https://stackoverflow.com/a/23705160/1325574 的 andersschuller 的解决方案)。可能有一些极端情况(类加载),这个实现不起作用,但对于最简单的情况,它可以工作。

我用我有限的 jmh 知识为此创建了一个小型性能测试: https://gist.github.com/picpromusic/4b19c718bec5a652731a65c7720ac5f8

“命名”结果是针对@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

如您所见,它比使用未命名的 lambda 大约慢 2 倍。所以在设置 -DnamedLambdasEnabled=true 时要小心。对我来说有趣的是,在 Real-lambda 上调用 toString 非常昂贵。也许有人可以解释一下,或者我的 jmh-test 很愚蠢。

代码如下:

/**
 * 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;
        }
    }
}

您还有其他解决方案吗?也许一些对性能没有影响的东西?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多