4

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.

Geoffrey De Smet
  • 26,223
  • 11
  • 73
  • 120
  • 1
    With JDK 1.8.0_51, the first snippet gives an error for me: `java.lang.IllegalAccessException: member is private: MyPerson.name/java.lang.String/getField`, which I think is expected. – Tunaki Dec 03 '15 at 15:12
  • @Tunaki for me java 1.8.0_51 it doesn't but the main() method inside the MyPerson class for me. If it's in another class in another package, I get the second exception. Haven't tried with a class in the same package. – Geoffrey De Smet Dec 03 '15 at 15:17
  • The javadoc indicates that some access checking is performed so this might fail if the caller is not in the same package (and `MyPerson` should be in the same package as `MyPerson` ;) ). It might be the field being `private` (in which case only `MyPerson` should have access to it) or a security manager that prevents access to the field. – Thomas Dec 03 '15 at 15:20
  • 2
    Btw, in 1.8.0_40 (but I assume this hasn't changed much) source shows that `getDirectField(REF_getField, refc, field)` is called by `findGetter` and the "direct" part indicates that it is not using the getter you provided but tries to directly read the field - and this should fail due to the field being private. – Thomas Dec 03 '15 at 15:23

2 Answers2

5

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.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
  • So I could bind one methodhandle per bean instance to speed things up? Interesting. Currently I haven't done that yet (as it's a major design change), but in this state, I am not seeing a performance gain, actually a big loss instead: [before](https://issues.jboss.org/secure/attachment/12395553/Selection_075.png] and [after](https://issues.jboss.org/secure/attachment/12395600/Selection_076.png) ([full jira log](https://issues.jboss.org/browse/PLANNER-483)) – Geoffrey De Smet Dec 03 '15 at 15:48
  • 1
    @GeoffreyDeSmet, actually you should not trust the profilers. I'm pretty sure their numbers are completely irrelevant and your bottleneck is probably in another place. Profilers sample at safepoints. The JIT-compiled code have safepoints not everywhere. No safepoints = wrong profiling data. – Tagir Valeev Dec 03 '15 at 15:51
  • Interesting. So sampling profiling points to the wrong suspect and instrumation profiling unfairly slows down low level methods. :/ – Geoffrey De Smet Dec 03 '15 at 15:53
  • For the record: with profiler attached, methodhandles are also very noticeably slower than plain old reflection (but the methodhandles are used ad described above, no binding of parameters). – Geoffrey De Smet Dec 03 '15 at 15:54
1

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.

Gergely Bacso
  • 14,243
  • 2
  • 44
  • 64