17

my question is strongly related to Explicit use of LambdaMetafactory in that thread, some very good examples are provided to use the LambdaMetafactory to access a static method of a class; however, I wonder what is the equivalent code to access a non static field of an existing bean instance. It seems really hard to find an example and every attempt I performed ended up in non working code.

This is the bean code:

class SimpleBean {
    private Object obj= "myCustomObject";
    private static Object STATIC_OBJECT = "myCustomStaticObject";
    public Object getObj() {
        return obj;
    }
    public void setObj(final Object obj) {
        this.obj = obj;
    }
    public static Object getStaticObj() {
        return STATIC_OBJECT;
    }
    public static void setStaticObj(final Object obj) {
        STATIC_OBJECT = obj;
    }
}

Here a working unit test that successfully access the static method "getStaticObj()":

    @Test
public void accessStaticMethod() throws Throwable
{
    MethodHandles.Lookup caller = MethodHandles.lookup();
    Method reflected = SimpleBean.class.getDeclaredMethod("getStaticObj");
    MethodHandle methodHandle = caller.unreflect(reflected);
    CallSite site = LambdaMetafactory.metafactory(caller,
            "get",
            MethodType.methodType(Supplier.class),
            MethodType.methodType(Object.class),
            methodHandle,
            MethodType.methodType(Object.class));
    MethodHandle factory = site.getTarget();
    Supplier r = (Supplier) factory.invoke();
    assertEquals( "myCustomStaticObject", r.get());
}

Now here my failing attempts to access the non static "getObj()" method:

    @Test
public void accessNonStaticMethodTestOne() throws Throwable
{
    SimpleBean simpleBeanInstance = new SimpleBean();

    MethodHandles.Lookup caller = MethodHandles.lookup();
    MethodHandle methodHandle = caller.bind(simpleBeanInstance, "getObj", MethodType.methodType(Object.class));
    assertEquals("myCustomObject", methodHandle.invoke());

    // This test fails here with exception:
    // java.lang.IllegalArgumentException: not a direct method handle
    CallSite site = LambdaMetafactory.metafactory(caller,
            "get",
            MethodType.methodType(Supplier.class),
            MethodType.methodType(Object.class),
            methodHandle,
            MethodType.methodType(Object.class));

    MethodHandle factory = site.getTarget();
    Supplier r = (Supplier) factory.invoke();
    assertEquals( "myCustomObject", r.get());

}

@Test
public void accessNonStaticMethodTwo() throws Throwable
{

    SimpleBean simpleBeanInstance = new SimpleBean();

    MethodHandles.Lookup caller = MethodHandles.lookup();

    Method reflected = SimpleBean.class.getDeclaredMethod("getObj");
    MethodHandle methodHandle = caller.unreflect(reflected);

    // This test fails here with exception:
    // java.lang.invoke.LambdaConversionException: Incorrect number of parameters
    CallSite site = LambdaMetafactory.metafactory(caller,
            "get",
            MethodType.methodType(Supplier.class),
            MethodType.methodType(Object.class),
            methodHandle,
            MethodType.methodType(Object.class));

    MethodHandle factory = site.getTarget();
    factory = factory.bindTo(simpleBeanInstance);
    Supplier r = (Supplier) factory.invoke();
    assertEquals( "myCustomObject", r.get());

}


@Test
public void accessNonStaticMethodThree() throws Throwable
{

    SimpleBean simpleBeanInstance = new SimpleBean();

    MethodHandles.Lookup caller = MethodHandles.lookup();

    Method reflected = SimpleBean.class.getDeclaredMethod("getObj");
    MethodHandle methodHandle = caller.unreflect(reflected);

    CallSite site = LambdaMetafactory.metafactory(caller,
            "get",
            MethodType.methodType(Supplier.class),
            MethodType.methodType(Object.class, SimpleBean.class),
            methodHandle,
            MethodType.methodType(Object.class, SimpleBean.class));

    MethodHandle factory = site.getTarget();

    //This test fails here with exception:
    // java.lang.IllegalArgumentException: no leading reference parameter: spike.LambdaBeanAccessAtRuntimeTest$SimpleBean@4459eb14
    factory = factory.bindTo(simpleBeanInstance);
    Supplier r = (Supplier) factory.invoke();
    assertEquals( "myCustomObject", r.get());

}

Every attempt has a different negative result, I really hope someone is abe to help me to have at least one test working fine.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
Francesco Cina
  • 907
  • 1
  • 11
  • 19
  • 1
    Let's see your attempts. Explain why you tried them and why they didn't work. – Sotirios Delimanolis Dec 22 '14 at 12:29
  • What's your question? – Bonifacio2 Dec 22 '14 at 12:31
  • I added the example code. The exceptions thrown at runtime are explained in the test code itself. – Francesco Cina Dec 22 '14 at 14:07
  • 2
    I don't know what you're trying to do, but I'd bet that you're using the wrong tool. `LambdaMetafactory` is a super-specialized tool for expert users, such as compiler writers. – Brian Goetz Dec 22 '14 at 15:50
  • 4
    @BrianGoetz One of my company's internal java libraries intensively use reflection to manipulate beans at runtime. After some studies I figured out that most of the reflection base code can be replaced by runtime generated lambda accessors. The advantage is an execution speed which is as fast as precompiled code! – Francesco Cina Dec 22 '14 at 16:06

1 Answers1

24

If you want to bind values to your lamba, you have to include these parameters to the invokedtype signature:

SimpleBean simpleBeanInstance = new SimpleBean();

MethodHandles.Lookup caller = MethodHandles.lookup();
MethodType getter=MethodType.methodType(Object.class);
MethodHandle target=caller.findVirtual(SimpleBean.class, "getObj", getter);
CallSite site = LambdaMetafactory.metafactory(caller,
    "get", // include types of the values to bind:
    MethodType.methodType(Supplier.class, SimpleBean.class),
    getter, target, getter);

MethodHandle factory = site.getTarget();
factory = factory.bindTo(simpleBeanInstance);
Supplier r = (Supplier) factory.invoke();
assertEquals( "myCustomObject", r.get());

Instead of binding a value you may use a Function which takes the bean as argument:

SimpleBean simpleBeanInstance = new SimpleBean();

MethodHandles.Lookup caller = MethodHandles.lookup();
MethodType getter=MethodType.methodType(Object.class);
MethodHandle target=caller.findVirtual(SimpleBean.class, "getObj", getter);
MethodType func=target.type();
CallSite site = LambdaMetafactory.metafactory(caller,
    "apply",
    MethodType.methodType(Function.class),
    func.erase(), target, func);

MethodHandle factory = site.getTarget();
Function r = (Function)factory.invoke();
assertEquals( "myCustomObject", r.apply(simpleBeanInstance));
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Hi Holger, your work fine for all the getters, it even works with primitive parameters. However, I'm able to apply it to setter methods. Based on your example I think that a BiConsumer interface should be used instead of Function, here my attempt: `MethodType setter = MethodType.methodType(Void.TYPE, Object.class); MethodHandle target = caller.findVirtual(SimpleBean.class, "setObj", setter); MethodType func = target.type(); CallSite site = LambdaMetafactory.metafactory(caller, "accept",MethodType.methodType(BiConsumer.class), func.generic(), target, func);` the factory throws and exception – Francesco Cina Dec 22 '14 at 23:01
  • 4
    While `func.generic()` is handy for a lot of use cases it doesn’t work well for `void` methods with the lambda metafactory. So you have to replace `func.generic()` with `func.changeParameterType(0, Object.class)`… – Holger Dec 31 '14 at 16:07