在Java 8中转换lambdas

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

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

 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的bug,但我很高兴错了!

这是一个说明行为的Github回购

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

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

产生一个ClassNotFoundException导致NoClassDefError 。 该类不被视为真正的类,并且例如这样的匿名类不会在ClassFileTransformer转换之外传递给ClassFileTransformer

总而言之,在处理匿名类时,检测API对我来说有点麻烦。 类似地, LambdaForm 传递给ClassFileTransformer但是所有参数都被设置为null这会破坏变换器类的契约。

对于您的示例,问题似乎是您返回null ; 返回classFileBuffer时问题就消失了什么是无操作。 然而,这不是ClassFileTransformer建议的,其中返回null是建议的方法:

格式良好的类文件缓冲区(转换的结果),如果不执行转换,则返回null

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

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

 Callable 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")); 

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