为什么以下第一个示例不起作用?
run(R::new);
方法R.run
没有被调用。run(new R());
方法R.run
被调用。两个示例都是可编译的。
public class ConstructorRefVsNew {
public static void main(String[] args) {
new ConstructorRefVsNew().run(R::new);
System.out.println("-----------------------");
new ConstructorRefVsNew().run(new R());
}
void run(Runnable r) {
r.run();
}
static class R implements Runnable {
R() {
System.out.println("R constructor runs");
}
@Override
public void run() {
System.out.println("R.run runs");
}
}
}
输出为:
R constructor runs
-----------------------
R constructor runs
R.run runs
在第一个示例中,调用R
构造函数,它返回lambda(不是对象):
但是,如何成功地编译示例呢?
答案 0 :(得分:56)
您的run
方法采用一个Runnable
实例,这说明了run(new R())
与R
实现一起工作的原因。
R::new
不等同于new R()
。它可以适合Supplier<Runnable>
(或类似的功能接口)的签名,但是R::new
不能用作通过R
类实现的Runnable
。
您可以使用run
的{{1}}方法的一个版本看起来像这样(但这会不必要地复杂):
R::new
为什么要编译?
因为编译器可以在构造函数调用中做出一个Runnable
,所以等同于此lambda表达式版本:
void run(Supplier<Runnable> r) {
r.get().run();
}
这些语句同样适用:
new ConstructorRefVsNew().run(() -> {
new R(); //discarded result, but this is the run() body
});
但是,您会注意到,使用Runnable runnable = () -> new R();
new ConstructorRefVsNew().run(runnable);
Runnable runnable2 = R::new;
new ConstructorRefVsNew().run(runnable2);
创建的Runnable
只是在其R::new
方法主体中调用new R()
。
有效使用方法引用来执行run
可以使用一个实例,例如这样(但在这种情况下,您肯定会直接使用R#run
实例):
r
答案 1 :(得分:22)
第一个示例用法method reference from java 8:
new ConstructorRefVsNew().run(R::new);
大致等于:(Java 8 lambda expression)
new ConstructorRefVsNew().run( () -> {new R();} );
结果是您仅创建R的实例,但不调用其run
方法。
答案 2 :(得分:8)
比较两个调用:
((Runnable)() -> new R()).run();
new R().run();
通过((Runnable)() -> new R())
或((Runnable) R::new)
创建新的Runnable
,它什么都不做 1 。
通过new R()
,您创建 R
类的实例,其中run
方法的定义明确。
1 实际上,它创建了一个R
对象,它对执行没有影响。
我正在考虑在不修改main
方法的情况下相同地对待2个调用。我们需要用run(Runnable)
重载run(Supplier<Runnable>)
。
class ConstructorRefVsNew {
public static void main(String[] args) {
new ConstructorRefVsNew().run(R::new);
System.out.println("-----------------------");
new ConstructorRefVsNew().run(new R());
}
void run(Runnable r) {
r.run();
}
void run(Supplier<Runnable> s) {
run(s.get());
}
static class R implements Runnable { ... }
}
答案 3 :(得分:7)
run
方法期望使用Runnable
。
简单的情况是new R()
。在这种情况下,您知道结果是R
类型的对象。 R
本身是可运行的,它具有run
方法,而Java就是这样。
但是当您通过R::new
时,正在发生其他情况。您所说的是创建一个与Runnable
兼容的匿名对象,该对象的run
方法将运行您传递给它的操作。
您通过的操作不是R
的{{1}}方法。该操作是run
的构造函数。因此,就像您已将其传递给一个匿名类一样:
R
(并非所有细节都相同,但这是最接近的“经典” Java构造)。
new Runnable() {
public void run() {
new R();
}
}
被调用时,将调用R::new
。没什么,没什么。
答案 4 :(得分:0)
在这里我只花两分钱就可以给出一个更易读的答案,因为人们对Java lambda世界是陌生的。
R::new
使用的是{8},它来自java8,它使您可以重用现有的方法定义并像lambdas一样传递它们。因此,当您编写method reference
时,实际上意味着Runnable::new
,结果为lambda; () -> new R()
正在调用类new R()
的构造函数,并返回该类的实例,结果是R
的实例。我们现在清楚它们是什么,但是它们如何工作(为什么它们可以编译)?
对于R
,很容易理解发生了什么,我将不加解释地离开。
对于new R()
代表Runnable::new
,我们需要知道() -> new R()
是我们所说的Runnable
,而功能接口是只有一种方法的接口,当我们将lambda传递给接受功能性接口作为参数的方法时,lambda必须与该接口的签名匹配,并且lambda中的操作会填充到该方法的主体中。
FunctionalInterface
中的Runnable
看起来像这样:
JDK11
lambda @FunctionalInterface
public interface Runnable {
public abstract void run();
}
与方法签名兼容-不接受任何内容也不返回任何内容,因此代码可以正常工作,在这种情况下,传入参数的runTime对象如下所示:
() -> new R()
现在我们知道为什么在这种情况下,仅触发Runnable instance with run method {
public void run() {
new R();
};
}
的构造。
我们可以像这样用lambda实现R
的作用:
run(new R())
我终于意识到,给定的lambda new ConstructorRefVsNew().run(() -> System.out.println("R.run runs"));
实际上与Runnable::new
接口兼容的问题非常棘手。您可以定义一个名为Runnable
的自定义功能接口或执行相同操作的任何操作
Foo
在这种情况下,@FunctionalInterface
public interface Foo{
public abstract void run();
}
public class ConstructorRefVsNew {
public static void main(String[] args) {
new ConstructorRefVsNew().run(R::new);
}
void run(Foo f) {
f.run();
}
}
仍然可以很好地工作,而R::new
无法通过,这表明问题不是什么大问题,而是一个有趣的巧合。