我们如何通过 getClass().getName() 找到源文件中的 lambda 位置?

How could we locate lambda position at the source file by there `getClass().getName()`?

我们有一些在线日志,它们看起来像 com.xxx.yyy.SomeClass$$Lambda345/7654321@qwert。 这些日志指向一些lambda函数(功能接口),如Runnable runnable = () -> {},但是SomeClass有很多lambda函数,所以需要在源文件中找到它们的具体行,以便定位错误.

顺便说一句,我们发现那些 lambda 函数的所有日志看起来都是 $package.$ClassName$$Lambda$index/$number@hashCode,并且相同的 lambda 在 class 之前具有相同的 $index/$number重新编译。

这是不可能的。 class 是在运行时生成的,并不反映触发 class 生成的代码位置。观察到的索引号反映了 class 生成的顺序,而不是代码中 lambda 表达式的出现。

只要程序的行为没有改变,看起来好像这个数字和代码位置之间有一个稳定的映射,但我们可以通过创建一个故意改变其行为的程序来轻松地反驳这一点:

import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.nio.file.Paths;

public class LambdaClassGeneration {
    public static void main(String[] args) {
        if(args.length == 0) {
            runMyself();
            return;
        }

        boolean even = args[0].equals("even");

        for(int i = 0; i < 2; i++, even = !even) {
            Runnable r;
            StackTraceElement e;
            if(even) {
                r = () -> System.out.println("even");
                e = new Exception().getStackTrace()[0];
            }
            else {
                r = () -> System.out.println("odd");
                e = new Exception().getStackTrace()[0];
            }
            r.run();
            System.out.println("created at "+e);
            System.out.println(r.getClass());
        }
    }

    private static void runMyself() {
        String[] cmd = {
            Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
            "-cp", System.getProperty("java.class.path"),
            MethodHandles.lookup().lookupClass().getName(),
            "arg"
        };
        ProcessBuilder p = new ProcessBuilder().inheritIO();

        for(int i = 0; i < 2; i++) try {
            System.out.println("Run " + i);
            cmd[cmd.length-1] = i%2 == 0? "even": "odd";
            p.command(cmd).start().waitFor();
            System.out.println();
        }
        catch(IOException | InterruptedException ex) {
            ex.printStackTrace();
            return;
        }
    }
}

此程序使用不同的参数运行两次,"even""odd",以表现出不同的行为,影响对 Runnable 的对象评估 lambda 表达式的顺序在运行时实现。

它打印出如下内容:

Run 0
even
created at LambdaClassGeneration.main(LambdaClassGeneration.java:20)
class LambdaClassGeneration$$Lambda/0x0000000800b90840
odd
created at LambdaClassGeneration.main(LambdaClassGeneration.java:24)
class LambdaClassGeneration$$Lambda/0x0000000800b91440

Run 1
odd
created at LambdaClassGeneration.main(LambdaClassGeneration.java:24)
class LambdaClassGeneration$$Lambda/0x0000000800b90840
even
created at LambdaClassGeneration.main(LambdaClassGeneration.java:20)
class LambdaClassGeneration$$Lambda/0x0000000800b91440

清楚地表明第一个生成的 class 得到索引 1,第二个生成的 class 得到索引 2 并且 class 名称中没有任何内容暗示我们是否正在查看“偶数”或“奇数”可运行。