问题
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.
回答1:
You are mixing the things. What you need is:
MethodHandle getterMethodHandle = lookup.findVirtual(MyPerson.class,
"getName", MethodType.methodType(String.class));
MethodHandle setterMethodHandle = lookup.findVirtual(MyPerson.class,
"setName", MethodType.methodType(void.class, String.class));
The findGetter
and findSetter
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. Using findGetter
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 like getName
, you'll still need a findVirtual
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.
回答2:
If you check the documentation of Lookup, you will see that:
Each method handle created by a factory method is the functional equivalent of a particular bytecode behavior
For example the getter lookup:
lookup.findGetter(C.class,"f",FT.class)
gets translated into:
(T) this.f;
Hence the strange error NoSuchFieldError: name
, as you were referencing a private field from outside. For getters/setters you should go for findVirtual
.
来源:https://stackoverflow.com/questions/34069386/methodhandle-to-a-getter-setter-from-another-class-gives-a-nosuchfielderror