Search code examples
javareflectionmethodhandle

MethodHandle to a getter/setter from another class gives a NoSuchFieldError


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.


Solution

  • 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.