tutorial - java funciones lambda ejemplos
Uso explĂcito de LambdaMetafactory (2)
Estoy intentando usar explícitamente LambdaMetafactory.metafactory, no puedo entender por qué funciona solo con la interfaz funcional de Runnable. Por ejemplo, este código hace lo que se espera (imprime "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");
}
}
El problema surge cuando trato de usar una interfaz funcional diferente, como el Proveedor. El siguiente código no funciona:
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)
¿No deberían los dos fragmentos de código funcionar de manera similar, cuál es el problema en el segundo fragmento de código?
Además, el siguiente código, que debería ser equivalente, funciona bien:
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";
}
}
Editar
Otra solución que evita cambiar el tipo de devolución del método es definir una nueva interfaz funcional:
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";
}
Este es otro ejemplo con nombres de variables más fáciles de entender:
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 + ".");
}
}
Debido a que LambdaMetafactory
crea una clase de fábrica sintética que luego se usa para crear una interfaz de destino, callSiteType
tiene un tipo de este método factory create()
. Este método create()
se llama implícitamente por invokedynamic
. LambdaMetafactory
devuelve un CallSite
que tiene una referencia de método al método create. Para las lambdas con cierres, llamarás a la fábrica como factory.create(capturedValue1, ..., capturedValueN)
y entonces debes modificar callSiteType
consecuencia.
La diferencia entre Runnable y el Proveedor es que el Proveedor usa un tipo genérico.
En tiempo de ejecución, el proveedor no tiene un método String get (), tiene Object get (). Pero el método que implementa devuelve un String. Necesitas distinguir entre esos 2 tipos. Me gusta esto:
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";
}
}