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.
Advertisement
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.erase(), target, func); MethodHandle factory = site.getTarget(); Function r = (Function)factory.invoke(); assertEquals( "myCustomObject", r.apply(simpleBeanInstance));