【发布时间】:2014-09-07 09:33:40
【问题描述】:
这一行
((UnaryOperator<Integer>)o->o).toString();
在类中的任何位置编写并使用 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
就其本身而言,这并没有什么特别值得注意的,它是 Eclipse 的 Java 8 编译器中的另一个错误。但是,我对失败的细节很感兴趣。如果我们启用 jdk.internal.lambda.dumpProxyClasses 系统属性并检索生成的 lambda 类代码,使用 javap 对其进行解析将显示该类定义了两个相同的 apply 方法,其中一个标记为 bridge 方法:
{
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
}
我了解泛型需要桥接方法以保持向后兼容性;但是,我无法理解 Eclipse 中的错误如何迫使 JDK 合成有缺陷的方法对。
为了比较,如果我们将 Java 行稍微更改为:
((Object)((UnaryOperator<Integer>)o->o)).toString();
然后我们只得到一个非桥接方法,并且没有失败:
{
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
}
难道这其实是JDK的一个bug,却不是javac惹的祸?
我在 OS X 和带有 Java 8 补丁的 Eclipse Kepler SR2 上使用 javac 1.8.0_20。
更新:引导方法调用
Eclipse 编译器负责发出正确的invokedynamic 引导方法调用(lambda 元工厂)。这是失败案例的引导方法参数的样子:
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;
在 Brian 的帮助下,我现在很清楚上面的最后两行会导致错误:
-
#47上的数字 1 表示“只有一种桥接方法”; -
#48上的(Ljava/lang/Object;)Ljava/lang/Object;描述了桥接方法的签名,显然与主签名相同。
为了比较,这是可行的情况:
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;
这里使用了更简单的metafactory 方法,并且没有创建桥接方法。
【问题讨论】:
标签: java eclipse lambda java-8