Francesco Cina Francesco Cina - 5 months ago 27
Java Question

Java access bean methods with LambdaMetafactory

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.

Answer

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.generic(), target, func);

MethodHandle factory = site.getTarget();
Function r = (Function)factory.invoke();
assertEquals( "myCustomObject", r.apply(simpleBeanInstance));
Comments