I'm trying to explicitly use LambdaMetafactory.metafactory, I can't understand why it works only with the Runnable functional interface. For Example this code does what it is expected (it prints "hello world"):
public class MetafactoryTest {
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup caller = MethodHandles.lookup();
MethodType methodType = MethodType.methodType(void.class);
MethodType invokedType = MethodType.methodType(Runnable.class);
CallSite site = LambdaMetafactory.metafactory(caller,
"run",
invokedType,
methodType,
caller.findStatic(MetafactoryTest.class, "print", methodType),
methodType);
MethodHandle factory = site.getTarget();
Runnable r = (Runnable) factory.invoke();
r.run();
}
private static void print() {
System.out.println("hello world");
}
}
The problem arise when I try to use a different functional interface, such as Supplier. The following code does not work:
public class MetafactoryTest {
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup caller = MethodHandles.lookup();
MethodType methodType = MethodType.methodType(String.class);
MethodType invokedType = MethodType.methodType(Supplier.class);
CallSite site = LambdaMetafactory.metafactory(caller,
"get",
invokedType,
methodType,
caller.findStatic(MetafactoryTest.class, "print", methodType),
methodType);
MethodHandle factory = site.getTarget();
Supplier<String> r = (Supplier<String>) factory.invoke();
System.out.println(r.get());
}
private static String print() {
return "hello world";
}
}
Exception in thread "main" java.lang.AbstractMethodError: metafactorytest.MetafactoryTest$$Lambda$1/258952499.get()Ljava/lang/Object;
at metafactorytest.MetafactoryTest.main(MetafactoryTest.java:29)
Shouldn't the two snippet of code work in a similar way, which is the problem in the second snippet of code?
Moreover the following code, that should be equivalent, works well:
public class MetafactoryTest {
public static void main(String[] args) throws Throwable {
Supplier<String> r = (Supplier<String>) () -> print();
System.out.println(r.get());
}
private static String print() {
return "hello world";
}
}
Edit
Another solution that avoids to change the method return type is to define a new functional interface:
public class MetafactoryTest {
@FunctionalInterface
public interface Test {
String getString();
}
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup caller = MethodHandles.lookup();
MethodType methodType = MethodType.methodType(String.class);
MethodType invokedType = MethodType.methodType(Test.class);
CallSite site = LambdaMetafactory.metafactory(caller,
"getString",
invokedType,
methodType,
caller.findStatic(MetafactoryTest.class, "print", methodType),
methodType);
MethodHandle factory = site.getTarget();
Test r = (Test) factory.invoke();
System.out.println(r.getString());
}
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:
I hope it helps someone.
This is another example with a more easy to understand variable names:
Because
LambdaMetafactory
creates a synthetic factory class that then is used to create target interface,callSiteType
has a type of this factorycreate()
method. Thiscreate()
method is called implicitly byinvokedynamic
-LambdaMetafactory
returns aCallSite
that has a method reference to the create method. For lambdas with closures you will call the factory likefactory.create(capturedValue1, ..., capturedValueN)
and so you must modifycallSiteType
accordingly.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: