Holger as usual is correct. It actually took me a while to understand what is going on, please treat this as an amendment to the other very good answer. To understand this, I had to do 3 different examples.
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");
}
}
}
If you run this, it will fail with java.lang.IllegalAccessException: no private access for invokespecial...
. The documentation gives proper directions of why this happens:
In general, the conditions under which a method handle may be looked up for a method M are no more restrictive than the conditions under which the lookup class could have compiled, verified, and resolved a call to M.
That same documentation explains this also:
The caller class against which those restrictions are enforced is known as the lookup class.
In our case lookup class
is FindSpecialFailure
and as such, this would be used to be able to tell if method go
from Son.class
could be compiled, verified, and resolved.
You can think about it simpler, a bit. Would you be able (in theory) to create an invokeSpecial
byte code instruction in FindSpecialFailure::main
and invoke it? Again, in theory. You could create it there:
invokeSpecial Son.go:()V
Can you invoke it though? Well, no; specifically, in my understanding, this rule would be broken:
If the symbolic reference names a class (not an interface), then that class is a superclass of the current class.
Obviously Son
is not a superclass of FindSpecialFailure
.
To prove my point, you could change the above code to (second example):
// 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");
}
}
}
This time everything will work just fine, because (from the same specification):
Otherwise, if C is an interface and the class Object contains a declaration of a public instance method with the same name and descriptor as the resolved method, then it is the method to be invoked.
How do I fix the first example though? (FindSpecialFailure
) You need to add an interesting option:
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();
}
}
If you go to the same documentation, you will find:
In some cases, access between nested classes is obtained by the Java compiler by creating an wrapper method to access a private method of another class in the same top-level declaration. For example, a nested class C.D can access private members within other related classes such as C, C.D.E, or C.B, but the Java compiler may need to generate wrapper methods in those related classes. In such cases, a Lookup object on C.E would be unable to those private members. A workaround for this limitation is the Lookup.in method, which can transform a lookup on C.E into one on any of those other classes, without special elevation of privilege.
The third example somehow starts to look more like your examples:
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");
}
}
}
The point of this one is that the documentation of findSpecial
says about the first parameter:
refc the class or interface from which the method is accessed.
That is why it will print Son
, and not Parent
.
Armed with this, your example becomes easier to understand:
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();
}
}
}
The lookup class is Son.class
and method resolution and refc
(there the class or interface from which the method is accessed) is GranFather
. So the resolution does start with GrandFather::thinking
, but since you can't call super.super
methods in java, that is "downgraded" to Father::thinking
.
All I could suggest here was to use .in
to solve this, I was not aware of privateLookupIn
.