Explicit use of LambdaMetafactory
The difference between Runnable and Supplier is that Supplier uses a generic type.
At runtime Supplier doesn't have a String get() method, it has Object get(). But the method you implement returns a String. You need to distinguish between those 2 types. Like this:
public class MetafactoryTest {
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup caller = MethodHandles.lookup();
MethodType methodType = MethodType.methodType(Object.class);
MethodType actualMethodType = MethodType.methodType(String.class);
MethodType invokedType = MethodType.methodType(Supplier.class);
CallSite site = LambdaMetafactory.metafactory(caller,
"get",
invokedType,
methodType,
caller.findStatic(MetafactoryTest.class, "print", actualMethodType),
methodType);
MethodHandle factory = site.getTarget();
Supplier<String> r = (Supplier<String>) factory.invoke();
System.out.println(r.get());
}
private static String print() {
return "hello world";
}
}
I had a situation where I needed to call a function passing some parameter to it. Similar to @Sahil Gupta question. I managed to solve it using a BiFunction with some adjustments:
public void testFunctionWithParameter() throws Throwable {
SimpleBean simpleBean = new SimpleBean();
MethodHandles.Lookup caller = MethodHandles.lookup();
MethodType invokedType = MethodType.methodType(BiFunction.class);
MethodType biFunc = MethodType.methodType(String.class, String.class);
MethodHandle target = caller.findVirtual(SimpleBean.class, "simpleFunction", biFunc);
MethodType func = target.type();
CallSite site = LambdaMetafactory.metafactory(
caller,
"apply",
invokedType,
func.generic(),
target,
MethodType.methodType(String.class, SimpleBean.class, String.class)
);
BiFunction<SimpleBean, String, String> fullFunction = (BiFunction<SimpleBean, String, String>) site.getTarget().invokeExact();
System.out.println(fullFunction.apply(simpleBean, "FOO"));
}
private class SimpleBean {
public String simpleFunction(String in) {
return "The parameter was " + in;
}
}
I hope it helps someone.
This is another example with a more easy to understand variable names:
public class Demo {
public static void main(String[] args) throws Throwable {
Consumer<String> consumer = s -> System.out.println("CONSUMED: " + s + ".");
consumer.accept("foo");
MethodHandles.Lookup caller = MethodHandles.lookup();
MethodType lambdaBodyMethodType = MethodType.methodType(void.class, String.class);
MethodHandle lambdaBody = caller.findStatic(
Demo.class, "my$lambda$main$0", lambdaBodyMethodType);
// Because of the type erasure we must use Object here
// instead of String (Consumer<String> -> Consumer).
MethodType functionalInterfaceMethodType =
MethodType.methodType(void.class, Object.class);
// we must return consumer, no closure -> no additional parameters
MethodType callSiteType = MethodType.methodType(Consumer.class);
CallSite site = LambdaMetafactory.metafactory(
// provided by invokedynamic:
caller, "accept", callSiteType,
// additional bootstrap method arguments:
functionalInterfaceMethodType,
lambdaBody,
lambdaBodyMethodType);
MethodHandle factory = site.getTarget();
Consumer<String> r = (Consumer<String>) factory.invoke();
r.accept("foo");
r.accept("bar");
}
private static void my$lambda$main$0(String s) {
System.out.println("CONSUMED: " + s + ".");
}
}
Because LambdaMetafactory
creates a synthetic factory class that then
is used to create target interface, callSiteType
has a type of this factory create()
method. This create()
method is called implicitly by invokedynamic
- LambdaMetafactory
returns a CallSite
that has a method reference to the create method. For lambdas with closures you will call the factory like factory.create(capturedValue1, ..., capturedValueN)
and so you must modify callSiteType
accordingly.
Late to the game but LambdaMetaFactory almost drove me crazy. Very hard to see which parameter goes where. Made an example that shows the different types a bit more explicit by naming them according to their role.
class Instance {
public RetSub get(Par p) {
System.out.println("Yes");
return null;
}
}
static class Ret {}
static class RetSub extends Ret {}
static class Par {}
static class ParSub extends Par {}
interface If {
Ret method(ParSub p);
}
@Test
public void testInstance() throws Throwable {
Instance instance = new Instance();
CallSite site = LambdaMetafactory.metafactory(caller,
"method",
MethodType.methodType(If.class, Instance.class), //
MethodType.methodType(Ret.class, ParSub.class), //
caller.findVirtual(Instance.class, "get", MethodType.methodType(RetSub.class, Par.class)), //
MethodType.methodType(RetSub.class, ParSub.class));
MethodHandle factory = site.getTarget();
If iff = (If) factory.invoke(instance);
iff.method(new ParSub());
}
I think it was hard because I missed the semantics of the parameters.
invokedName
– Obviously the method name in the interfaceinvokedType
– This is the hard one. This is the MethodType of the MethodHandle returned by theCallSite.getTarget()
. The first argument is the type of the interface we're trying to make (If
), the second argument is only needed when we access an instance method and is the instance type (Instance
).samMethodType
– This is the exact method type of the function we try to implement. In this case 'Ret method(ParSub p)`.implMethod
– A method handle. It must be possible to convert the parameters and return type of the interface method we create to this method handle.instantiationMethodType
– A runtime check to verify some parameters and return types fall within narrower or the same types than the interface.