Suppose I have simple javabean MyPerson
with a name
getter and setter:
public class MyPerson {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Now I am running this main code that simply gets and sets that name
field:
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle getterMethodHandle = lookup.findGetter(MyPerson.class, "name", String.class);
MethodHandle setterMethodHandle = lookup.findSetter(MyPerson.class, "name", String.class);
MyPerson a = new MyPerson();
a.setName("Batman");
System.out.println("Name from getterMethodHandle: " + getterMethodHandle.invoke(a));
setterMethodHandle.invoke(a, "Robin");
System.out.println("Name after setterMethodHandle: " + a.getName());
}
If I add that main()
method on the class MyPerson
, I get what I expect:
Name from getterMethodHandle: Batman
Name after setterMethodHandle: Robin
If I add that same main()
method on another class in another package, I get this weird error:
Exception in thread "main" java.lang.NoSuchFieldException: no such field: batman.other.MyMain.name/java.lang.String/getField
at java.lang.invoke.MemberName.makeAccessException(MemberName.java:875)
at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:990)
at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:1373)
at java.lang.invoke.MethodHandles$Lookup.findGetter(MethodHandles.java:1022)
at batman.other.MyMain.main(MyMain.java:28)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.NoSuchFieldError: name
at java.lang.invoke.MethodHandleNatives.resolve(Native Method)
at java.lang.invoke.MemberName$Factory.resolve(MemberName.java:962)
at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:987)
Why? MyPerson
's getter/setters are public, so there's no reason why MyMain
shouldn't be to use them, even through MethodHandles.
Using JDK 8 with source/target level java 8.
If you check the documentation of Lookup, you will see that:
For example the getter lookup:
gets translated into:
Hence the strange error
NoSuchFieldError: name
, as you were referencing a private field from outside. For getters/setters you should go forfindVirtual
.You are mixing the things. What you need is:
The
findGetter
andfindSetter
methods do not try to find some getXXX or setXXX methods like in Java Beans. Don't forget that method handles are very low-level stuff. These methods actually build a method handle which does not point to an existing method, but just sets the field with given name. UsingfindGetter
you don't need to have an actual getter method in your class, but you must have a direct access to the field. If you want to use the getter method likegetName
, you'll still need afindVirtual
call.In general method handles are much more powerful than just references to the methods. For example, you can bind method handle to one or several parameters, so you will not need to specify them on the invocation.