javac生成的$$中的$$是什么意思?

ric*_*tie 6 java lambda anonymous-inner-class jar javac

当转换通过像DependencyFinder和java-callgraph这样的库生成的java调用图时,我发现java编译器为匿名函数,内部类等生成名称.

我已经找到了其中几个的含义(如果我错了,请纠正):

  • org.example.Bar$Foo指的是Foo,这是一个内在的类org.example.Bar.
  • org.example.Bar$1指的是在其中一个方法中声明的匿名类org.example.Bar.
  • org.example.Bar.lambda$spam$1()指的是在org.example.Bar.spam()方法内声明的lambda .

但是,我也发现:

  1. org.example.Bar$$Lambda$2.args$1
  2. org.example.Bar$$Lambda$2.call()
  3. org.example.Bar$$Lambda$7.lambdaFactory$()
  4. org.example.Bar$$Lambda$7.get$Lambda()

上面的四个名字是指什么?double dollar($$)是什么意思?

Hol*_*ger 6

lambda表达式的类不是javac生成的,而是由JRE在运行时创建的.他们的名字完全没有说明,你不能依赖任何命名方案.

但显然,Oracle目前的JRE具有可识别的模式.它附加$$Lambda$n到定义类的名称,而是n一个递增的数字,它反映了运行时的创建顺序,而不是编译代码的任何属性.

您可以使用以下程序验证:

public class Test {
    public static void main(String... args) {
        if(args.length==0) {
            final boolean meFirst = Math.random()<0.5;
            if(meFirst) {
                Runnable r=Test::main;
                System.out.println("first run:\t"+r.getClass());
            }
            main("second run");
            if(!meFirst) {
                Runnable r=Test::main;
                System.out.println("first run:\t"+r.getClass());
            }
        }
        else {
            Runnable r=Test::main;
            System.out.println(args[0]+":\t"+r.getClass());
            if(args[0].equals("second run")) main("last run");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

根据随机meFirst标志的状态,它将打印

first run:  class Test$$Lambda$1
second run: class Test$$Lambda$2
last run:   class Test$$Lambda$2
Run Code Online (Sandbox Code Playgroud)

要么

second run: class Test$$Lambda$1
last run:   class Test$$Lambda$1
first run:  class Test$$Lambda$2
Run Code Online (Sandbox Code Playgroud)

它表明第一个生成的类总是获取数字1,无论它是在第一次main调用中实例化的前两个方法引用之一,还是第三个方法引用,在第一次递归中实例化.此外,第3次执行总是遇到与第2次相同的类,因为它是相同的方法引用表达式(注意:distinct 表达式,因为所有表达式的目标是相同的)并且该类被重用.

根据版本的不同,您可能会进一步看到/number附加到名称的内容,这些内容暗示名称确实无关紧要,因为每个类都有另一个唯一标识符(它们是所谓的"匿名类",您不能找到via ClassLoader和name).

args$n这些类中的字段名称代表第n个捕获值.由于LambdaMetafactory不了解捕获变量的实际名称,因此除了生成这样的名称之外别无选择.


但正如所说,这是一个实现工件.只要为每个定义类中的每个创建站点生成一个新类,就可以维护这样的命名模式.但由于规范允许任意共享/重用表示等效lambda表达式(执行相同操作)和方法引用(针对相同方法)的类和实例,因此每种实现策略都无法实现这样的命名模式.