在 Java 8 中转换 lambdas

2022-09-03 14:25:53

Java 8 似乎生成了表示 lambda 表达式的类。例如,代码:

  Runnable r = app::doStuff;

大致表现为:

  // $FF: synthetic class
  final class App$$Lambda$1 implements Runnable {
    private final App arg$1;

    private App$$Lambda$1(App var1) {
        this.arg$1 = var1;
    }

    private static Runnable get$Lambda(App var0) {
        return new App$$Lambda$1(var0);
    }

    public void run() {
        this.arg$1.doStuff();
    }
  }

据我所知,代码是在运行时生成的。现在,假设有人想将代码注入到上述类的方法中。到目前为止,实验结果混合了和:runNoClassDefFoundVerifyError

java.lang.NoClassDefFoundError: App$$Lambda$2
    at App$$Lambda$2/1329552164.run(Unknown Source)
    at App.main(App.java:9)
Caused by: java.lang.ClassNotFoundException: App$$Lambda$2
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 2 more

这是针对以下各项运行的:

$ java -version
java version "1.8.0_51"
Java(TM) SE Runtime Environment (build 1.8.0_51-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode)

这甚至在将任何新的字节码推送到类中之前。

这是意料之中的吗?闻起来像一个JDK错误,但我很高兴错了!

下面是一个 Github 存储库,用于说明该行为


答案 1

对我来说,这似乎是JVM中的一个错误。系统类装入器尝试按名称定位转换后的类。但是,lambda 表达式是通过匿名类加载来加载的,其中存在以下条件:

clazz.getClassLoader()
     .loadClass(clazz.getName().substring(0, clazz.getName().indexOf('/')))

生成一个结果,即 .该类不被视为真正的类,例如,此类非优类不会传递给重新转换的外部。ClassNotFoundExceptionNoClassDefErrorClassFileTransformer

总而言之,在处理匿名类时,检测 API 对我来说有点错误。类似地,s 被传递给 s,但所有参数都与 set to what 破坏了转换器类的协定。LambdaFormClassFileTransformerclassFileBuffernull

对于您的示例,问题似乎是您返回 ;返回什么是无操作时,问题消失了。然而,这不是建议,返回是推荐的方法:nullclassFileBufferClassFileTransformernull

格式正确的类文件缓冲区(转换的结果),或者如果不执行任何转换。null

对我来说,这似乎是HotSpot中的一个错误。您应该向 OpenJDK 报告此问题。

总而言之,完全可以检测匿名加载的类,正如我在代码操作库 Byte Buddy 中演示的那样。与普通检测相比,它需要一些不幸的调整,但运行时支持它。下面是一个在库中作为单元测试成功运行的示例:

Callable<String> lambda = () -> "foo";

Instrumentation instrumentation = ByteBuddyAgent.install();
ClassReloadingStrategy classReloadingStrategy = ClassReloadingStrategy.of(instrumentation)
    .preregistered(lambda.getClass());
ClassFileLocator classFileLocator = ClassFileLocator.AgentBased.of(instrumentation, 
     lambda.getClass());

assertThat(lambda.call(), is("foo"));

new ByteBuddy()
  .redefine(lambda.getClass(), classFileLocator)
  .method(named("call"))
  .intercept(FixedValue.value("bar"))
  .make()
  .load(lambda.getClass().getClassLoader(), classReloadingStrategy);

assertThat(lambda.call(), is("bar"));

答案 2

错误提交已被Oracle的人接受,并被跟踪为JDK-8145964。这并不是一个解决方案,但似乎是一个真正的运行时问题。


推荐