Явное использование LambdaMetafactory
Я пытаюсь явно использовать LambdaMetafactory.metafactory, я не понимаю, почему он работает только с функциональным интерфейсом Runnable. Например, этот код делает то, что ожидается (он печатает "привет мир" ):
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");
}
}
Проблема возникает, когда я пытаюсь использовать другой функциональный интерфейс, такой как Поставщик. Следующий код не работает:
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)
Не должны ли два фрагмента кода работать аналогичным образом, что является проблемой во втором фрагменте кода?
Кроме того, следующий код, который должен быть эквивалентен, хорошо работает:
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
Другим решением, которое позволяет избежать изменения типа возвращаемого метода, является определение нового функционального интерфейса:
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";
}
Ответы
Ответ 1
Разница между Runnable и поставщиком заключается в том, что поставщик использует общий тип.
В среде выполнения поставщик не имеет метода String get(), он имеет Object get(). Но метод, который вы реализуете, возвращает String. Вам нужно различать эти 2 типа. Вот так:
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";
}
}
Ответ 2
Это еще один пример с более понятными именами переменных:
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 + ".");
}
}
Потому что LambdaMetafactory
создает синтетический класс factory, который затем
используется для создания целевого интерфейса, callSiteType
имеет тип этого метода factory create()
. Этот метод create()
неявно называется invokedynamic
- LambdaMetafactory
возвращает CallSite
, который имеет ссылку на метод метода create. Для лямбда с закрытием вы вызываете factory как factory.create(capturedValue1, ..., capturedValueN)
, поэтому вы должны соответствующим образом изменить callSiteType
.
Ответ 3
У меня была ситуация, когда мне нужно было вызвать функцию, передав ей какой-то параметр. Похоже на вопрос @Sahil Gupta. Мне удалось решить эту проблему с помощью BiFunction с некоторыми изменениями:
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;
}
}
Надеюсь, это кому-нибудь поможет.