I want to print hi GrandFather
But it seems to print hi father. And I am not understand how to diff the use between findSpecial
and findVirtual
I want someone can help me. Thank you
class GrandFather{
void thinking(){
System.out.println("hi GrandFather");
}
}
class Father extends GrandFather{
void thinking(){
System.out.println("hi Father");
}
}
class Son extends Father{
void thinking(){
MethodType mt=MethodType.methodType(void.class);
//MethodHandle mh=MethodHandles.lookup().findVirtual(GrandFather.class,"thinking",mt).bindTo(this);
MethodHandle mh;
mh = MethodHandles.lookup().findSpecial(GrandFather.class,"thinking",mt,getClass());
mh.invoke(this);
}
}
public static void main(String[] args){
(new MethodHandleTest().new Son()).thinking();
}
The last argument to findSpecial
specifies the context class for the invocation. You specified getClass()
, which will result in Son.class
. A super
invocation from Son
can only end up in Father
, just like an ordinary super.thinking()
call in source code.
You need to specify Father.class
as context class, as Father
is allowed to perform a super
invocation to GrandFather
. If you do this without further changes, you’d get an exception like java.lang.IllegalAccessException: no private access for invokespecial…
. You have to change the context of your lookup()
object, to be able to access private
features of Father
:
MethodType mt = MethodType.methodType(void.class);
MethodHandle mh = MethodHandles.lookup().in(Father.class)
.findSpecial(GrandFather.class, "thinking", mt, Father.class);
mh.invoke(this);
This works, because Son
and Father
are inner classes of the same top level class, so accessing each other’s private members is allowed. If they were not classes within the same top level class, in(…)
would change the context class but clear the private access permission. In that case, only Java 9 and newer have an official solution:
MethodType mt = MethodType.methodType(void.class);
MethodHandle mh = MethodHandles.privateLookupIn(Father.class, MethodHandles.lookup())
.findSpecial(GrandFather.class, "thinking", mt, Father.class);
mh.invoke(this);
This works when Father
and Son
are in the same module or Father
’s module has opened Father
’s package to Son
’s module for Reflection.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With