只是一个理论问题,我目前没有实际用例。
假设我的API接受函数引用作为参数,我想通过'::'语法直接从代码中提取它,或者通过反射收集匹配函数,存储在某些Map中并有条件地调用。
可以通过编程方式将method
转换为Consumer<String>
?
Map<String, Consumer<String>> consumers = new HashMap<>();
consumers.put("println", System.out::println);
Method method = PrintStream.class.getMethod("println", String.class);
consumers.put("println", makeFunctionReference(method));
...
myapi.feedInto(consumers.get(someInput.getConsumerId()));
更新:
虽然目前提供的答案中的解决方案不满意,但在得到关于LambdaMetaFactory
的提示之后我试图编译此代码
public class TestImpl {
public static void FnForString(String arg) {}
}
public class Test {
void test() {
List<String> strings = new ArrayList<>();
Consumer<String> stringConsumer = TestImpl::FnForString;
strings.stream().forEach(stringConsumer);
strings.stream().forEach(TestImpl::FnForString);
stringConsumer.accept("test");
}
}
并且只将Test类送入CFR反编译器后,我正在追踪:
public class Test {
void test() {
ArrayList strings = new ArrayList();
Consumer<String> stringConsumer =
(Consumer<String>)LambdaMetafactory.metafactory(
null, null, null,
(Ljava/lang/Object;)V,
FnForString(java.lang.String),
(Ljava/lang/String;)V)();
strings.stream().forEach(stringConsumer);
strings.stream().forEach(
(Consumer<String>)LambdaMetafactory.metafactory(
null, null, null,
(Ljava/lang/Object;)V,
FnForString(java.lang.String ),
(Ljava/lang/String;)V)());
stringConsumer.accept("test");
}
}
其中我看到了:
(Ljava/lang/Object;)V
(和其他人)是什么。它应与metafactory()参数中的MethodType
匹配。另外 - 反编译器'吃掉/隐藏'某些东西,但现在似乎在获取函数引用期间调用了方法。并且......提供了Test和TestImpl类,CFR重建了与我编译完全相同的代码。
答案 0 :(得分:5)
你可以用这样的反射做到这一点:
consumers.put("println", s -> {
try {
method.invoke(System.out, s);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
});
但是您希望您的代码使用方法引用(即使用invokedynamic
指令)编译为相同的代码,您可以使用MethodHandle
。这没有反射的开销,所以它会表现得更好。
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType methodType = MethodType.methodType(void.class, String.class);
MethodHandle handle = lookup.findVirtual(PrintStream.class, "println", methodType);
consumers.put("println", s -> {
try {
handle.invokeExact(System.out, s);
} catch (Throwable e) {
throw new RuntimeException(e);
}
});
consumers.get("println").accept("foo");
在此代码中,首先检索MethodHandles.Lookup
个对象。该类可用于创建MethodHandle
个对象。然后创建一个MethodType
对象,它表示由方法句柄接受和返回的参数和返回类型:在这种情况下,它是一个返回void(因此void.class
)并接受一个String的方法(因此String.class
)。最后,通过在println
类上找到PrintStream
方法获得句柄。
您可以参考this question(和this one),了解有关MethodHandle
的更多信息。
答案 1 :(得分:3)
最简单但最不具备性能的方法就是将方法包装到消费者手中。
final Method m = ...
final T target = ...
Consumer<String> c = (arg1) => m.invoke(t, arg1);
使用LambdaMetaFactory可能会产生更优化的代码,但考虑到您通过Map
进行调度,可能不值得。
这在某种程度上可以用'1-liner'方式进行
如果你真的想模仿字节码所做的事情,那对于单线程的充分折磨的定义来说这是唯一的。你的反编译器在某种程度上对你有用。
无需处理异常
这是因为在字节码级别上不存在已检查异常的概念。这可以使用为您执行sneaky rethrow的静态帮助程序方法进行模拟。
我不知道反编译器输出中的(Ljava / lang / Object;)V(和其他)是什么。它应该与metafactory()参数中的MethodType匹配。另外 - 反编译器'吃掉/隐藏'某些东西,但现在似乎在获取函数引用期间调用了方法。
它看起来像invokedynamic调用的伪代码。 JVM真正做的是更复杂,并且无法在java中简明扼要地表达,因为它涉及延迟初始化。最好阅读java.lang.invoke package description以了解真实情况。
与链接阶段等效的java级别将把CalleSite的dynamicInvoker
MH放入static final MethodHandle
字段并调用其invokeExact
方法。
(offtop)即使在编译代码中获取函数引用也至少有一个函数调用 - 通常这在性能关键代码中可能并不是一个不显着的廉价操作。
如上所述,链接阶段相当于将methodhandle放在静态字段中,然后再调用它,而不是尝试再次解析该方法。
答案 2 :(得分:2)
反编译器在你的代码上失败了,但是,除了重新创建原始的Java 8方法引用之外,没有正确的反编译,这不是你感兴趣的。
lambda表达式和方法引用是使用score=[]
score[rcount][ccount]=num
字节代码指令编译的,该指令在Java编程语言中没有等价物。等效代码如下:
invokedynamic
除了在public static void main(String... arg) {
Consumer<String> consumer=getConsumer();
consumer.accept("hello world");
}
static Consumer<String> getConsumer() {
try {
MethodHandles.Lookup lookup=MethodHandles.lookup();
MethodType consumeString = MethodType.methodType(void.class, String.class);
return (Consumer<String>)LambdaMetafactory.metafactory(lookup, "accept",
MethodType.methodType(Consumer.class, PrintStream.class),
consumeString.changeParameterType(0, Object.class),
lookup.findVirtual(PrintStream.class, "println", consumeString), consumeString)
.getTarget().invokeExact(System.out);
}
catch(RuntimeException | Error e) { throw e; }
catch(Throwable t) { throw new BootstrapMethodError(t); }
}
内完成的所有操作最初都由一条getConsumer()
指令处理,该指令会处理所有涉及的invokedynamic
和MethodHandle
实例,例如常量和第一次评估的结果获得了内在的缓存工具。您无法使用普通的Java源代码对其进行建模。
仍然,上面的MethodType
方法返回的Consumer<String>
与表达式getConsumer()
(当分配给System.out::println
时)完全等效,具有相同的行为和性能特性
您可以学习“Translation of Lambda Expressions” by Brian Goetz以深入了解其工作原理。此外,API documentation of LambdaMetafactory
非常详尽。