【发布时间】:2017-03-09 07:09:36
【问题描述】:
在一个包中 (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);
}
}
第一个实现使用匿名类,它可以正常工作。另一方面,第二个编译正常,但在运行时失败,在尝试访问Applicable 接口时抛出由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 表达式要么像匿名类一样工作,要么给出编译时错误,这将更有意义。所以,我只是想知道这里发生了什么。
我尝试删除超级接口并在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 元工厂是如何工作的,但我认为这可能是一个关键的区别。
我还尝试像这样在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
【问题讨论】:
-
您能提供完整的堆栈跟踪吗?抛出 Error 听起来很可疑。
-
根据您的描述,我不确定“DUP”关闭是否合法。如果我是您,我会创建一个完整的最小可行示例并将其放入您的问题中。如果您可以显示 one 段代码,编译出 one 文件导致此错误,则 DUP 不匹配;你应该要求重新打开。
-
@GhostCat 我认为没有两个包是不可能得到这个错误的,超级接口一定是不可见的。
-
我认为多文件 MCVE 是可以接受的 - 重要的部分是 minimal 部分...最小不一定意味着单文件,但它确实意味着“不要填充我的浏览器缓存”。
-
我会重新打开。我可以用
javac重现,而不是用 Eclipse,也许是 bug。
标签: java generics lambda package-private