Runnable :: new与new Runnable()

时间:2019-01-07 10:15:02

标签: java java-8 runnable constructor-reference

为什么以下第一个示例不起作用?

  • 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(不是对象):

但是,如何成功地编译示例呢?

5 个答案:

答案 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无法通过,这表明问题不是什么大问题,而是一个有趣的巧合。

相关问题