Bub*_*tan 22 java generics lambda package-private
在一个包(a)中,我有两个功能接口:
package a;
@FunctionalInterface
interface Applicable<A extends Applicable<A>> {
void apply(A self);
}
Run Code Online (Sandbox Code Playgroud)
-
package a;
@FunctionalInterface
public interface SomeApplicable extends Applicable<SomeApplicable> {
}
Run Code Online (Sandbox Code Playgroud)
apply超级接口中的方法采用selfas,A否则,如果Applicable<A>使用if ,则类型在包外不可见,因此无法实现该方法.
在另一个包(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);
}
}
Run Code Online (Sandbox Code Playgroud)
第一个实现使用匿名类,它没有问题.另一方面,第二个编译很好,但在运行时失败,因为它试图访问接口时java.lang.BootstrapMethodError引起a .java.lang.IllegalAccessErrorApplicable
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
Run Code Online (Sandbox Code Playgroud)
我认为如果lambda表达式像匿名类一样工作或者给出编译时错误会更有意义.所以,我只是想知道这里发生了什么.
我尝试删除超级接口并在此处声明方法SomeApplicable:
package a;
@FunctionalInterface
public interface SomeApplicable {
void apply(SomeApplicable self);
}
Run Code Online (Sandbox Code Playgroud)
这显然使其工作,但允许我们看到字节码中的不同之处.
lambda$0从lambda表达式编译的合成方法在两种情况下看起来都相同,但我可以发现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
Run Code Online (Sandbox Code Playgroud)
从#59变化(La/Applicable;)V到(La/SomeApplicable;)V.
我真的不知道lambda metafactory是如何工作的,但我认为这可能是一个关键的区别.
我也尝试过这样明确地声明apply方法SomeApplicable:
package a;
@FunctionalInterface
public interface SomeApplicable extends Applicable<SomeApplicable> {
@Override
void apply(SomeApplicable self);
}
Run Code Online (Sandbox Code Playgroud)
现在该方法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
Run Code Online (Sandbox Code Playgroud)
apa*_*gin 12
据我所知,JVM做的一切都是正确的.
当apply声明方法Applicable而不是声明方法时SomeApplicable,匿名类应该工作,而lambda不应该.我们来看看字节码.
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
Run Code Online (Sandbox Code Playgroud)
javac 生成接口方法apply(Applicable)和overriden方法的实现apply(SomeApplicable).Applicable除方法签名外,这两种方法都不是指不可访问的接口.也就是说,在匿名类的代码中的任何地方Applicable都没有解析接口(JVMS§5.4.3).
请注意,apply(Applicable)可以成功调用Test,因为在解析invokeinterface指令期间不解析方法签名中的类型(JVMS§5.4.3.4).
通过invokedynamic使用bootstrap方法执行字节码获得lambda的实例LambdaMetafactory.metafactory:
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
Run Code Online (Sandbox Code Playgroud)
用于构造lambda的静态参数是:
void (a.Applicable);void (a.SomeApplicable).所有这些参数都在invokedynamic引导过程中得到解决(JVMS§5.4.3.6).
现在关键点:解析MethodType解析其方法描述符中给出的所有类和接口(JVMS§5.4.3.5).特别是,JVM尝试a.Applicable代表Test类解析,并失败IllegalAccessError.然后,根据规范invokedynamic,错误被包装进去BootstrapMethodError.
要解决此问题IllegalAccessError,您需要在可公开访问的SomeApplicable界面中显式添加桥接方法:
public interface SomeApplicable extends Applicable<SomeApplicable> {
@Override
void apply(SomeApplicable self);
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,lambda将实现apply(SomeApplicable)方法而不是apply(Applicable).相应的invokedynamic指令将引用(La/SomeApplicable;)VMethodType,它将成功解析.
注意:仅改变SomeApplicable界面是不够的.您必须Test使用新版本重新编译SomeApplicable才能invokedynamic使用正确的MethodTypes 生成.我已经在从8u31到最新的9-ea的几个JDK上验证了这一点,并且所讨论的代码没有错误.
| 归档时间: |
|
| 查看次数: |
1643 次 |
| 最近记录: |