Lambda表达式在运行时因java.lang.BootstrapMethodError而失败
在一个包(Lambda表达式在运行时因java.lang.BootstrapMethodError而失败,java,generics,lambda,package-private,Java,Generics,Lambda,Package Private,在一个包(a)中,我有两个功能接口: package a; @FunctionalInterface interface Applicable<A extends Applicable<A>> { void apply(A self); } 第一个实现使用一个匿名类,它可以正常工作。另一方面,第二个编译很好,但在运行时抛出由java.lang.IllegalAccessError引起的java.lang.BootstrapMethodError失败,因为它试
a
)中,我有两个功能接口:
package a;
@FunctionalInterface
interface Applicable<A extends Applicable<A>> {
void apply(A self);
}
第一个实现使用一个匿名类,它可以正常工作。另一方面,第二个编译很好,但在运行时抛出由java.lang.IllegalAccessError
引起的java.lang.BootstrapMethodError
失败,因为它试图访问适用的
接口
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表达式的工作方式与匿名类类似,或者出现编译时错误,则更有意义。所以,我只是想知道这里发生了什么
我尝试删除superinterface并在
someapplicative
中声明方法,如下所示:
package a;
@FunctionalInterface
public interface SomeApplicable {
void apply(SomeApplicable self);
}
package a;
@FunctionalInterface
public interface SomeApplicable extends Applicable<SomeApplicable> {
@Override
void apply(SomeApplicable self);
}
这显然使它起作用,但允许我们看到字节码的不同之处
从lambda表达式编译的合成lambda$0
方法在这两种情况下似乎是相同的,但我可以在bootstrap方法下的方法参数中发现一个差异
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/applicative;)V
更改为(La/someapplicative;)V
我真的不知道lambda元工厂是如何工作的,但我认为这可能是一个关键的区别
我还尝试在
someapplible
中明确声明apply
方法,如下所示:
package a;
@FunctionalInterface
public interface SomeApplicable {
void apply(SomeApplicable self);
}
package a;
@FunctionalInterface
public interface SomeApplicable extends Applicable<SomeApplicable> {
@Override
void apply(SomeApplicable self);
}
在我看来,JVM做的一切都是正确的 当
apply
方法在applible
中声明,而不是在someapplible
中声明时,匿名类应该工作,lambda不应该工作。让我们检查字节码
匿名类测试$1
javac
生成接口方法apply(适用)
的实现和覆盖的方法apply(某些适用)
。除方法签名外,两种方法均未引用不可访问的接口适用
。也就是说,适用的
接口在匿名类的代码中的任何地方都没有解析
请注意,可以从Test
成功调用apply(适用)
,因为在解析invokeinterface
指令期间不会解析方法签名中的类型
兰姆达
lambda的实例是通过使用bootstrap方法执行字节码获得的:
用于构造lambda的静态参数有:
void(a.applicative)
李>
void(a.someapplicative)
invokedynamic
bootstrap过程中解析
现在关键的一点是:要解析MethodType,解析其方法描述符中给出的所有类和接口。特别是,JVM试图代表Test
类解析a.applicative
,但由于IllegalAccessError
而失败。然后,根据的规范,将错误包装到BootstrapMethodError
架桥法
要解决IllegalAccessError
,您需要在可公开访问的someapplicative
接口中显式添加桥接方法:
public interface SomeApplicable extends Applicable<SomeApplicable> {
@Override
void apply(SomeApplicable self);
}
公共接口部分适用扩展{
@凌驾
无效申请(适用于自己);
}
在这种情况下,lambda将实现apply(某些适用的)
方法,而不是apply(适用的)
。相应的invokedynamic
指令将引用(La/someapplicative;)V
MethodType,这将被成功解析
注意:仅仅更改一些适用的接口是不够的。您必须使用新版本的
someapplicative
重新编译Test
,以便使用正确的方法类型生成invokedynamic
。我已经在从8u31到最新的9-ea的几个JDK上验证了这一点,所讨论的代码工作正常。您能提供完整的stacktrace吗?抛出错误听起来很可疑。根据您的描述,我不确定“DUP”关闭是否合法。如果我是你,我会创建一个完整的最小可行的例子,并把它放到你的问题中。如果您可以显示由一个文件编译的一段代码导致此错误,则DUP不匹配;您应该要求重新打开。@GhostCat我认为如果没有两个包,就不可能出现此错误,超级界面必须不可见。我认为多文件MCVE是可以接受的-重要的部分是最小的部分。。。minimal并不一定意味着只有一个文件,但它确实意味着“不要填充我的浏览器缓存”。我会重新打开。我可以用javac
复制,而不是用Eclipse,可能是bug。如果用Eclipse编译,桥接方法的解决方案似乎不起作用。现在,我用javac尝试了一下,它的效果和预期的一样。出于某种原因,Eclipse编译器使用altMetafactory
和BRIDGES
标志(和(La/applicative;)V
作为桥接方法类型),这会导致出现相同的错误。我发现的另一个简单的解决方法是将applicative
声明为applicative
。然后参数类型将是公共的,因为它被擦除到对象
。给出一个错误或使用一些可能的解决方法将是一个更好的选择,而不仅仅是默默地接受非法代码。
public void apply(a.SomeApplicable);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String a
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public void apply(a.Applicable);
Code:
0: aload_0
1: aload_1
2: checkcast #5 // class a/SomeApplicable
5: invokevirtual #6 // Method apply:(La/SomeApplicable;)V
8: return
BootstrapMethods:
0: #36 invokestatic java/lang/invoke/LambdaMetafactory.metafactory
Method arguments:
#37 (La/Applicable;)V
#38 invokestatic b/Test.lambda$main$0:(La/SomeApplicable;)V
#39 (La/SomeApplicable;)V
public interface SomeApplicable extends Applicable<SomeApplicable> {
@Override
void apply(SomeApplicable self);
}