我正在尝试显式使用LambdaMetafactory.metafactory,我无法理解为什么它仅适用于Runnable功能接口。例如,此代码执行预期的操作(它打印“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");
}
}
当我尝试使用其他功能界面时会出现问题,例如供应商。以下代码不起作用:
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";
}
}
修改
避免更改方法返回类型的另一个解决方案是定义一个新的功能接口:
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";
}
答案 0 :(得分:15)
Runnable和Supplier之间的区别在于供应商使用通用类型。
在运行时,供应商没有String get()方法,它有Object get()。但是您实现的方法返回一个String。您需要区分这两种类型。像这样:
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";
}
}
答案 1 :(得分:1)
这是另一个更容易理解变量名称的例子:
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
创建了一个合成工厂类
用于创建目标接口,callSiteType
具有此工厂create()
方法的类型。 create()
隐式调用此invokedynamic
方法 - LambdaMetafactory
返回一个CallSite
,其中包含对create方法的方法引用。对于带闭包的lambda,你会像factory.create(capturedValue1, ..., capturedValueN)
一样打电话给工厂,所以你必须相应地修改callSiteType
。
答案 2 :(得分:0)
我遇到了需要调用将一些参数传递给它的函数的情况。类似于@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;
}
}
我希望它能对某人有所帮助。