霍尔格像往常一样是正确的。实际上我花了一段时间才明白发生了什么,请将其视为对另一个非常好的答案的修正。为了理解这一点,我不得不做 3 个不同的例子。
public class FindSpecialFailure {
public static void main(String[] args) {
try {
MethodType mt = MethodType.methodType(void.class);
Lookup l = MethodHandles.lookup();
MethodHandle mh = l.findSpecial(Son.class, "go", mt, Son.class);
mh.invoke(new Son());
} catch (Throwable t) {
t.printStackTrace();
}
}
static class Parent {
void go() {
System.out.println("parent");
}
}
static class Son extends Parent {
void go() {
System.out.println("son");
}
}
}
如果你运行它,它将失败并显示java.lang.IllegalAccessException: no private access for invokespecial...
. 该文档给出了为什么会发生这种情况的正确方向:
通常,可以为方法 M 查找方法句柄的条件并不比查找类可以编译、验证和解析对 M 的调用的条件更具限制性。
同样的文档也解释了这一点:
实施这些限制的调用者类称为查找类。
在我们的例子lookup class
中FindSpecialFailure
,它可以用来判断方法go
fromSon.class
是否可以被编译、验证和解析。
你可以想得更简单一点。您是否能够(理论上)在其中创建invokeSpecial
字节码指令FindSpecialFailure::main
并调用它?再次,理论上。你可以在那里创建它:
invokeSpecial Son.go:()V
你可以调用它吗?好吧,不;具体来说,据我了解,这条规则将被打破:
如果符号引用命名一个类(不是接口),则该类是当前类的超类。
显然Son
不是 的超类FindSpecialFailure
。
为了证明我的观点,您可以将上面的代码更改为(第二个示例):
// example 1
public class FindSpecialInterface {
public static void main(String[] args) {
try {
MethodType mt = MethodType.methodType(void.class);
Lookup l = MethodHandles.lookup();
// <---- Parent.class
MethodHandle mh = l.findSpecial(Parent.class, "go", mt, Son.class);
mh.invoke(new Son());
} catch (Throwable t) {
t.printStackTrace();
}
}
// <---- this is now an interface
interface Parent {
default void go() {
System.out.println("parent");
}
}
static class Son implements Parent {
public void go() {
System.out.println("son");
}
}
}
这次一切都会很好,因为(来自相同的规范):
否则,如果 C 是一个接口并且类 Object 包含一个公共实例方法的声明,该方法与解析的方法具有相同的名称和描述符,那么它就是要调用的方法。
我该如何解决第一个例子呢?( FindSpecialFailure
) 您需要添加一个有趣的选项:
public static void main(String[] args) {
try {
MethodType mt = MethodType.methodType(void.class);
// <--- Notice the .in(Son.class) -->
Lookup l = MethodHandles.lookup().in(Son.class);
MethodHandle mh = l.findSpecial(Son.class, "go", mt, Son.class);
mh.invoke(new Son());
} catch (Throwable t) {
t.printStackTrace();
}
}
如果您访问相同的文档,您会发现:
在某些情况下,嵌套类之间的访问是由 Java 编译器通过创建包装方法来访问同一顶级声明中另一个类的私有方法来获得的。例如,嵌套类 CD 可以访问其他相关类(如 C、CDE 或 CB)中的私有成员,但 Java 编译器可能需要在这些相关类中生成包装器方法。在这种情况下,CE 上的 Lookup 对象将无法访问这些私有成员。解决此限制的方法是 Lookup.in 方法,它可以将 CE 上的查找转换为任何其他类的查找,而无需特殊的特权提升。
第三个示例开始看起来更像您的示例:
public class FinSpecialMoveIntoSon {
public static void main(String[] args) {
new Son().invokeMe();
}
static class Parent {
public void go() {
System.out.println("parent");
}
}
static class Son extends Parent {
void invokeMe() {
try {
MethodType mt = MethodType.methodType(void.class);
Lookup l = MethodHandles.lookup();
MethodHandle mh = l.findSpecial(Son.class, "go", mt, Son.class);
mh.invoke(new Son());
} catch (Throwable t) {
t.printStackTrace();
}
}
public void go() {
System.out.println("son");
}
}
}
这一点的重点是findSpecial
关于第一个参数的文档说:
refc 从中访问方法的类或接口。
这就是为什么它会打印Son
,而不是Parent
。
有了这个,你的例子就更容易理解了:
static class Son extends Father {
void thinking() {
try {
MethodType mt = MethodType.methodType(void.class);
MethodHandle mh = MethodHandles.lookup().findSpecial(GrandFather.class, "thinking", mt, getClass());
mh.invoke(this);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
查找类是Son.class
和方法解析和refc
(从那里访问方法的类或接口)是GranFather
。所以决议确实以. GrandFather::thinking
_super.super
Father::thinking
我在这里所能建议的只是用来.in
解决这个问题,我不知道privateLookupIn
.