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)
  • 直接负责实施
  • lambda表达式的有效方法类型:
    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);
    }