Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java8 retrieving lambda setter from class

I'm trying to get a lambda method handle for the setter of a private field, but for some reason, the setter cannot be found.

This is the functional interface I am using:

@FunctionalInterface
public interface ISetter<T, R> {
    void set(T object, R value);
}

And this the method used to fetch the setter:

public ISetter getSetter(Class clazz, String fieldName, Class fieldType) throws Throwable {

    MethodHandles.Lookup caller = MethodHandles.lookup();
    MethodType setter = MethodType.methodType(Void.class, fieldType);
    MethodHandle target = caller.findVirtual(clazz, computeSetterName(fieldName), setter);
    MethodType func = target.type();

    CallSite site = LambdaMetafactory.metafactory(
            caller,
            "set",
            MethodType.methodType(ISetter.class),
            func.generic(),
            target,
            func
    );

    MethodHandle factory = site.getTarget();
    ISetter r = (ISetter) factory.invoke();

    return r;
}

All setters are, by convention, named the same: "setField(...)". This is my test class:

public class TestEntity {

    private Long id;


    public TestEntity(Long id) {
        this.id = id;
    }


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

But when I execute this method for "id" and "Long.class" I get the follow exception:

Exception in thread "main" java.lang.NoSuchMethodException: no such method: de.cyclonit.exercise.TestEntity.setId(Long)Void/invokeVirtual
    at java.lang.invoke.MemberName.makeAccessException(MemberName.java:871)
    at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1003)
    at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:1381)
    at java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:859)
    at de.cyclonit.exercise.AccessorFactory.getSetter(AccessorFactory.java:41)
    at de.cyclonit.exercise.Main.main(Main.java:21)
Caused by: java.lang.NoSuchFieldError: method resolution failed
    at java.lang.invoke.MethodHandleNatives.resolve(Native Method)
    at java.lang.invoke.MemberName$Factory.resolve(MemberName.java:975)
    at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1000)
    ... 4 more

I don't understand where this error is coming from. The method "void setId(Long)" does exist.


public String computeSetterName(String fieldName) {
    return "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
}

And the call:

ISetter setter = accessorFactory.getSetter(TestEntity.class, "id", Long.class);
like image 794
Cyclonit Avatar asked May 14 '17 18:05

Cyclonit


1 Answers

There are a few mistakes in your code.

First, you are using Void.class, you should use void.class instead.

Second, func.generic returns (Object,Object)Object, what you really want is func.erase, so the return type is retained ((Object,Object)void).

Third, it advisable not to use raw types, I would propose the following implementation instead:

public static <T, R> ISetter<T, R> getSetter(Class<T> clazz, String fieldName,
        Class<R> fieldType) throws Throwable {

    MethodHandles.Lookup caller = MethodHandles.lookup();
    MethodType setter = MethodType.methodType(void.class, fieldType);
    MethodHandle target = caller.findVirtual(clazz, computeSetterName(fieldName), setter);
    MethodType func = target.type();

    CallSite site = LambdaMetafactory.metafactory(
            caller,
            "set",
            MethodType.methodType(ISetter.class),
            func.erase(),
            target,
            func
    );

    MethodHandle factory = site.getTarget();
    ISetter<T, R> r = (ISetter<T, R>) factory.invoke();

    return r;
}

Which works for me: http://ideone.com/KKx10r

like image 68
Jorn Vernee Avatar answered Oct 08 '22 21:10

Jorn Vernee