使用Eclipse编译器为lambda生成的重复方法

Mar*_*nik 3 java eclipse lambda java-8

这条线

((UnaryOperator<Integer>)o->o).toString();
Run Code Online (Sandbox Code Playgroud)

写在类中的任何地方,并使用Eclipse Kepler编译,在执行时到达该行时将导致失败:

java.lang.BootstrapMethodError: call site initialization exception
at java.lang.invoke.CallSite.makeSite(CallSite.java:328)
at java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:296)
at test.Test.main(Test.java:7)
Caused by: java.lang.ClassFormatError: Duplicate method name&signature in class file test/Test$$Lambda$1
at sun.misc.Unsafe.defineAnonymousClass(Native Method)
at java.lang.invoke.InnerClassLambdaMetafactory.spinInnerClass(InnerClassLambdaMetafactory.java:324)
at java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:194)
at java.lang.invoke.LambdaMetafactory.altMetafactory(LambdaMetafactory.java:474)
at java.lang.invoke.CallSite.makeSite(CallSite.java:301)
... 2 more
Run Code Online (Sandbox Code Playgroud)

就其本身而言,这不是什么值得注意的,而是Eclipse Java 8编译器中的另一个错误.但是,我对失败的细节很感兴趣.如果我们启用jdk.internal.lambda.dumpProxyClasses系统属性并检索生成的lambda类代码,解析它将javap显示该类有两个相同的apply方法定义,其中一个标记为桥接方法:

{
  public java.lang.Object apply(java.lang.Object);
    descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: checkcast     #14                 // class java/lang/Integer
         4: invokestatic  #20                 // Method test/Test.lambda$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
         7: areturn

  public java.lang.Object apply(java.lang.Object);
    descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
    flags: ACC_PUBLIC, ACC_BRIDGE
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: checkcast     #14                 // class java/lang/Integer
         4: invokestatic  #20                 // Method test/Test.lambda$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
         7: areturn
}
Run Code Online (Sandbox Code Playgroud)

我知道泛型需要桥接方法以保持向后兼容性; 但是,我无法理解Eclipse中的错误如何迫使JDK合成有缺陷的方法对.

为了比较,如果我们稍微改变我们的Java行:

((Object)((UnaryOperator<Integer>)o->o)).toString();
Run Code Online (Sandbox Code Playgroud)

然后我们得到一个非桥接方法,没有失败:

{
  public java.lang.Object apply(java.lang.Object);
    descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: checkcast     #14                 // class java/lang/Integer
         4: invokestatic  #20                 // Method test/Test.lambda$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
         7: areturn
}
Run Code Online (Sandbox Code Playgroud)

难道这可能是JDK中的一个错误,但它不是由它引起的javac

javac 1.8.0_20在OS X和Eclipse Kepler SR2上使用Java 8补丁.

更新:bootstrap方法调用

Eclipse编译器负责发出正确的invokedynamic bootstrap方法调用(lambda metafactory).这是失败案例的bootstrap方法参数的样子:

BootstrapMethods:
      0: #39 invokestatic java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
        Method arguments:
          #41 (Ljava/lang/Object;)Ljava/lang/Object;
          #44 invokestatic test/Test.lambda$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
          #45 (Ljava/lang/Integer;)Ljava/lang/Integer;
          #46 4
          #47 1
          #48 (Ljava/lang/Object;)Ljava/lang/Object;
Run Code Online (Sandbox Code Playgroud)

由于Brian的帮助,现在我很清楚上面的最后两行会造成错误:

  • 数字1 #47表示"有一种桥接方法";
  • (Ljava/lang/Object;)Ljava/lang/Object;on #48描述了桥接方法的签名,这显然与主签名相同.

为了比较,这是工作案例:

BootstrapMethods:
      0: #53 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
        Method arguments:
          #55 (Ljava/lang/Object;)Ljava/lang/Object;
          #58 invokestatic test/Test.lambda$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
          #59 (Ljava/lang/Integer;)Ljava/lang/Integer;
Run Code Online (Sandbox Code Playgroud)

这里使用了更简单的metafactory方法,并且没有创建桥接方法.

Bri*_*etz 6

基于您发布的堆栈跟踪,这几乎肯定是Eclipse代码生成中的错误,而不是JDK.你可以从捕获 lambda(由ecj生成)javap的代码列表中找到它.我认为你会发现它会调用备用metafactory()来处理异常情况,例如可序列化lambda,附加标记接口由lambda对象实现,或者由目标接口未处理的桥接方法. altMetafactory

作为参考,唯一明确需要额外桥接的情况是

  1. 当目标接口需要桥接但是使用较旧的javac编译时,因此接口本身不存在桥接,或者
  2. 当目标类型和附加接口之间的交互需要桥接时.

两者都是严重的角落案件,应该极少发生.