MethodHandle to a getter/setter from another class

2019-08-11 06:55发布

问题:

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.