Runnable::new 与新的 Runnable()
Posted
技术标签:
【中文标题】Runnable::new 与新的 Runnable()【英文标题】:Runnable::new vs new Runnable() 【发布时间】:2019-06-01 23:57:19 【问题描述】:为什么下面的第一个例子不起作用?
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(不是对象):
但那怎么可能编译成功呢?
【问题讨论】:
请注意Runnable runnable = R::new;
runnable instanceof R
-> false
我并不特别了解 Java,但 new
通常表明您想要分配一些您承诺会自己清理的内存。 R::new
听起来就像一个工厂方法,是 R 中的一个静态函数,它创建并返回一个 Runnable 的实例。如果此实例未通过将其分配给变量来捕获,则它可能会在超出范围时被清除。
【参考方案1】:
您的run
方法采用Runnable
实例,这就解释了为什么run(new R())
与R
实现一起使用。
R::new
不等同于new R()
。它可以符合Supplier<Runnable>
(或类似功能接口)的签名,但R::new
不能用作Runnable
与您的R
类实现。
可以采用R::new
的run
方法版本可能如下所示(但这会不必要地复杂):
void run(Supplier<Runnable> r)
r.get().run();
为什么会编译?
因为编译器可以从构造函数调用中生成Runnable
,这将等效于这个 lambda 表达式版本:
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);
但是,如您所见,使用R::new
创建的Runnable
只是在其run
方法体中调用new R()
。
有效地使用方法引用来执行R#run
可以使用这样的实例(但在这种情况下,您肯定更愿意直接使用r
实例):
R r = new R();
new ConstructorRefVsNew().run(r::run);
【讨论】:
我们是否应该假设,java 编译器是从我的方法 run(Runnable r) -> run(SupplierR::new
中生成了() -> new R();
。【参考方案2】:
第一个例子:
new ConstructorRefVsNew().run(R::new);
或多或少等同于:
new ConstructorRefVsNew().run( () -> new R(); );
效果是你只是创建了一个 R 的实例,但不调用它的run
方法。
【讨论】:
这意味着,我有两个嵌套的 runnable。仅在第一个方法上调用 run。【参考方案3】:比较两个调用:
((Runnable)() -> new R()).run();
new R().run();
通过((Runnable)() -> new R())
或((Runnable) R::new)
,您创建一个新的Runnable
,它什么都不做1。
通过new R()
,您创建R
类的实例,其中run
方法已明确定义。
1其实是创建了一个R
的对象,对执行没有影响。
我正在考虑在不修改 main
方法的情况下以相同方式处理 2 个调用。我们需要用run(Supplier<Runnable>)
重载run(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 ...
【讨论】:
【参考方案4】:run
方法需要 Runnable
。
简单的案例是new R()
。在这种情况下,您知道结果是R
类型的对象。 R
本身是一个可运行的,它有一个 run
方法,这就是 Java 的看法。
但是当您通过R::new
时,正在发生其他事情。你告诉它的是创建一个与Runnable
兼容的匿名对象,其run
方法运行你传递给它的操作。
你传递的操作不是R
的run
方法。该操作是R
的构造函数。因此,就像你给它传递了一个匿名类,比如:
new Runnable()
public void run()
new R();
(并非所有细节都相同,但这是最接近的“经典”Java 构造)。
R::new
,当被调用时,调用new R()
。不多也不少。
【讨论】:
【参考方案5】:我的两分钱在这里给出一个更具可读性的答案,因为人们是 java lambda 世界的新手。
这是什么
R::new
使用的是从 java8 中出现的 method reference
,让您可以重用现有的方法定义并像 lambdas 一样传递它们。所以当你写Runnable::new
的时候,其实就是() -> new R()
,结果是一个lambda;
new R()
正在调用类R
的构造函数,并返回该类的一个实例,结果是一个R
的实例。
我们现在清楚它们是什么,但是它们是如何工作的(为什么它们是可编译的)?
工作原理
对于new R()
,很容易理解发生了什么,我就不解释了。
对于代表() -> new R()
的Runnable::new
,我们需要知道Runnable
就是我们所说的FunctionalInterface
,而函数式接口是只有一个方法的接口,当我们将lambda传递给一个方法时接受函数式接口作为参数,lambda 必须匹配该接口的签名,并且 lambda 中的操作填充到该方法的主体。
JDK11
中的 Runnable
如下所示:
@FunctionalInterface
public interface Runnable
public abstract void run();
虽然 lambda () -> new R()
与方法签名兼容 - 不接受也不返回任何内容,因此代码可以正常工作,在这种情况下,传入参数的 runTime 对象如下所示:
Runnable instance with run method
public void run()
new R();
;
现在我们知道为什么在这种情况下,只触发了R
的构造。
还有什么
我们可以像这样使用 lambda 实现 run(new R())
所做的事情:
new ConstructorRefVsNew().run(() -> System.out.println("R.run runs"));
我终于意识到给定的 lambda 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
仍然可以正常工作,而 new R()
不能通过,这表明问题不是什么大问题,而是一个有趣的巧合。
【讨论】:
以上是关于Runnable::new 与新的 Runnable()的主要内容,如果未能解决你的问题,请参考以下文章
Callback 与新的 AsyncCallback(Callback) 有啥不同?
UIPickerView 与新的 UITableViewController
HttpClientFactory.Create 与新的 HttpClient