在一个包(a
)中,我有两个功能接口:
package a;
@FunctionalInterface
interface Applicable<A extends Applicable<A>> {
void apply(A self);
}
package a;
@FunctionalInterface
public interface SomeApplicable extends Applicable<SomeApplicable> {
}
超级接口中的apply
方法以self
作为A
参数,否则如果使用Applicable<A>
,则类型将在包外不可见,因此无法实现该方法。
在另一个包中(b
),我有以下Test
类:
package b;
import a.SomeApplicable;
public class Test {
public static void main(String[] args) {
// implement using an anonymous class
SomeApplicable a = new SomeApplicable() {
@Override
public void apply(SomeApplicable self) {
System.out.println("a");
}
};
a.apply(a);
// implement using a lambda expression
SomeApplicable b = (SomeApplicable self) -> System.out.println("b");
b.apply(b);
}
}
第一种实现使用匿名类,它可以正常工作。另一方面,第二种实现虽然能够成功编译,但在运行时会抛出一个 java.lang.BootstrapMethodError
,由于试图访问 Applicable
接口而引发了一个 java.lang.IllegalAccessError
。
Exception in thread "main" java.lang.BootstrapMethodError: java.lang.IllegalAccessError: tried to access class a.Applicable from class b.Test
at b.Test.main(Test.java:19)
Caused by: java.lang.IllegalAccessError: tried to access class a.Applicable from class b.Test
... 1 more
我认为如果lambda表达式能够像匿名类一样工作或者在编译时给出错误提示,那么它将更有意义。因此,我想知道这里发生了什么。
我尝试去掉超级接口并像这样在SomeApplicable
中声明方法:
package a;
@FunctionalInterface
public interface SomeApplicable {
void apply(SomeApplicable self);
}
这显然使它起作用,但允许我们看到字节码的不同之处。
从lambda表达式编译的合成lambda$0
方法在两种情况下似乎是相同的,但我可以在引导方法下的方法参数中发现一个区别。
Bootstrap methods:
0 : # 58 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:
#59 (La/Applicable;)V
#62 invokestatic b/Test.lambda$0:(La/SomeApplicable;)V
#63 (La/SomeApplicable;)V
#59
的更改从(La/Applicable;)V
变为(La/SomeApplicable;)V
。
我不太清楚lambda metafactory是如何工作的,但我认为这可能是一个关键区别。
我还尝试了像这样显式声明SomeApplicable
中的apply
方法:
package a;
@FunctionalInterface
public interface SomeApplicable extends Applicable<SomeApplicable> {
@Override
void apply(SomeApplicable self);
}
现在方法apply(SomeApplicable)
已经存在,编译器为apply(Applicable)
生成了桥接方法。但是,在运行时仍然会抛出相同的错误。
在字节码级别上,它现在使用的是LambdaMetafactory.altMetafactory
而不是LambdaMetafactory.metafactory
:
Bootstrap methods:
0 : # 57 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:
#58 (La/SomeApplicable;)V
#61 invokestatic b/Test.lambda$0:(La/SomeApplicable;)V
#62 (La/SomeApplicable;)V
#63 4
#64 1
#66 (La/Applicable;)V
javac
复现,但不能使用Eclipse,可能是bug。 - Sotirios Delimanolis