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::newrun 方法版本可能如下所示(但这会不必要地复杂):

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(Supplier r) 创建的? @user1722245 我编辑了答案。在这种情况下,编译器从R::new 中生成了() -&gt; 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)() -&gt; new R())((Runnable) R::new),您创建一个新的Runnable,它什么都不做1

通过new R(),您创建R 类的实例,其中run 方法已明确定义。


1其实是创建了一个R的对象,对执行没有影响。


我正在考虑在不修改 main 方法的情况下以相同方式处理 2 个调用。我们需要用run(Supplier&lt;Runnable&gt;) 重载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 方法运行你传递给它的操作。

你传递的操作不是Rrun方法。该操作是R 的构造函数。因此,就像你给它传递了一个匿名类,比如:

new Runnable() 

     public void run() 
         new R();
     

(并非所有细节都相同,但这是最接近的“经典”Java 构造)。

R::new,当被调用时,调用new R()。不多也不少。

【讨论】:

【参考方案5】:

我的两分钱在这里给出一个更具可读性的答案,因为人们是 java lambda 世界的新手。

这是什么

R::new 使用的是从 java8 中出现的 method reference,让您可以重用现有的方法定义并像 lambdas 一样传递它们。所以当你写Runnable::new的时候,其实就是() -&gt; new R(),结果是一个lambda; new R() 正在调用类R 的构造函数,并返回该类的一个实例,结果是一个R 的实例。

我们现在清楚它们是什么,但是它们是如何工作的(为什么它们是可编译的)?

工作原理

对于new R(),很容易理解发生了什么,我就不解释了。

对于代表() -&gt; new R()Runnable::new,我们需要知道Runnable就是我们所说的FunctionalInterface,而函数式接口是只有一个方法的接口,当我们将lambda传递给一个方法时接受函数式接口作为参数,lambda 必须匹配该接口的签名,并且 lambda 中的操作填充到该方法的主体。

JDK11 中的 Runnable 如下所示:

@FunctionalInterface
public interface Runnable 

    public abstract void run();

虽然 lambda () -&gt; 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

Promise.resolve 与新的 Promise(resolve)

Bootstrap 下拉菜单不与新的 Rails 布局切换

UIAlertView 显示方法与新的 UIAlertController 对象等效吗?