只是一个理论问题,我目前还没有实际的用例.
假设我的一些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");
}
}在只向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 (和其他)是什么。它应该在MethodType参数中与元角()匹配。另外-反编译器‘吃/隐藏’的东西,但现在似乎有调用的方法在获取函数引用。还有..。提供了测试类和TestImpl类之后,CFR就可以重新构造我编译的代码。
发布于 2015-11-20 19:47:02
你可以用这样的反射来做这个:
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.class)的方法。最后,通过在println类上找到PrintStream方法获得句柄。
发布于 2015-11-20 19:48:42
最简单的方法,尽管不一定是最有表现力的方法,就是将方法包装到消费者中。
final Method m = ...
final T target = ...
Consumer<String> c = (arg1) => m.invoke(t, arg1);使用LambdaMetaFactory可能会产生更好的代码,但考虑到您正在通过Map进行调度,这可能不值得。
这在某种程度上可以用“1-liner”的方式来实现。
如果您真的想模仿字节码所做的事情,那么这只适用于对一行行的足够严苛的定义。你的反编译者在某种程度上对你撒谎。
不需要异常处理。
这是因为字节码级别上不存在检查异常的概念。这可以通过为您执行鬼鬼祟祟地重投的静态助手方法来模拟。
我不知道什么是(Ljava/lang/Object;)V (和其他)在反编译器的输出中。它应该与元文件()参数中的MethodType匹配。另外-反编译器‘吃/隐藏’的东西,但现在似乎有调用的方法在获取函数引用。
它看起来像是发票动态调用的伪代码。JVM真正做的事情更复杂,不能用java简洁地表达,因为它涉及延迟初始化。最好读一读java.lang.invoke包描述来了解到底发生了什么。
相当于链接阶段的java级别将把CalleSite的dynamicInvoker MH放到一个static final MethodHandle字段中,并调用其invokeExact方法。
(offtop)即使在编译的代码中获得函数引用也至少是一个函数调用--一般来说,在性能关键代码中,这可能不是不明显的廉价操作。
如前所述,链接阶段相当于将方法句柄放在静态字段中一次,然后在以后调用它,而不是尝试第二次解析方法。
发布于 2015-11-24 18:10:01
反编译程序在您的代码上失败了,但是,除了重新创建原来的Java 8方法引用(这不是您感兴趣的内容)之外,无论如何也没有正确的反编译。
lambda表达式和方法引用是使用invokedynamic字节码指令编译的,在Java编程语言中没有等效的字节码指令。等价的代码将类似于:
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和MethodType实例当作常量来处理,并且第一次评估的结果得到了一个内在的缓存功能。您不能使用普通Java源代码对其进行建模。
不过,上面的Consumer<String>方法返回的getConsumer()与具有相同行为和性能特征的表达式System.out::println (当分配给Consumer<String>时)是完全等价的。
您可以学习Brian的“Lambda表达式的翻译”,以便更深入地了解它的工作原理。此外,LambdaMetafactory也相当详尽。
https://stackoverflow.com/questions/33834288
复制相似问题