霍尔格像往常一样是正确的。实际上我花了一段时间才明白发生了什么,请将其视为对另一个非常好的答案的修正。为了理解这一点,我不得不做 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... 失败。 The documentation 给出了发生这种情况的正确方向:
一般来说,可以为方法 M 查找方法句柄的条件并不比查找类可以编译、验证和解析对 M 的调用的条件更严格。
同样的文档也解释了这一点:
执行这些限制的调用者类称为查找类。
在我们的例子中,lookup class 是 FindSpecialFailure,因此,这将用于判断来自 Son.class 的方法 go 是否可以编译、验证和解析 .
你可以想得简单一点。你能(理论上)在FindSpecialFailure::main 中创建一个invokeSpecial 字节码指令并调用它吗?再次,理论上。你可以在那里创建它:
invokeSpecial Son.go:()V
你可以调用它吗? Well, no;具体来说,据我了解,这条规则会被打破:
如果符号引用命名一个类(不是接口),则该类是当前类的超类。
显然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");
}
}
}
这一次一切都会好起来的,因为 (from the same specification):
否则,如果 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();
}
}
如果你去the same documentation,你会发现:
在某些情况下,嵌套类之间的访问是由 Java 编译器通过创建包装方法来访问同一顶级声明中的另一个类的私有方法来获得的。例如,嵌套类 C.D 可以访问其他相关类(如 C、C.D.E 或 C.B)中的私有成员,但 Java 编译器可能需要在这些相关类中生成包装器方法。在这种情况下,C.E 上的 Lookup 对象将无法访问这些私有成员。此限制的解决方法是 Lookup.in 方法,它可以将 C.E 上的查找转换为任何其他类的查找,而无需特殊的特权提升。
第三个示例开始看起来更像您的示例:
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 的文档中提到了第一个参数:
引用从其访问方法的类或接口。
这就是为什么它会打印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开头,但由于您不能在java中调用super.super方法,因此“降级”为Father::thinking。
我在这里只能建议使用.in 来解决这个问题,我不知道privateLookupIn。