Runnable::new和new Runnable()的区别

63

为什么以下示例中的第一个示例无法正常工作?

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

但是这个例子怎么可能编译成功呢?


2
请注意:Runnable runnable = R::new; runnable instanceof R -> false - Prasad Karunagoda
我不太了解Java,但是new通常表示您想分配一些内存,并承诺自己会清理它。R::new听起来只是一个工厂方法,在R中创建并返回Runnable实例的静态函数。如果没有将此实例分配给变量进行捕获,它可能在超出范围时被清除。 - kevin
5个回答

57
你的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 <Runnable> r)? - user1722245
2
@user1722245 我编辑了答案。编译器在那个上下文中将 R::new 转换为 () -> {new R();} - ernest_k

24

第一个例子:

new ConstructorRefVsNew().run(R::new);

大致相当于:

new ConstructorRefVsNew().run( () -> {new R();} );
效果是你只创建了R的一个实例但没有调用它的run方法。

2
这意味着我有两个嵌套的可运行方法。在第一个方法中,只调用了run方法。 - user1722245

10

比较两个调用:

((Runnable)() -> new R()).run();
new R().run();

通过 ((Runnable)() -> new R())((Runnable) R::new),您可以创建一个无操作的新Runnable1

通过 new R(),您可以创建一个具有定义良好的run方法的R类实例


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 { ... }
}

7

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(),没有更多或更少的内容。

0

这里只是我的个人意见,为了让那些刚接触Java Lambda的人更容易理解。

这是什么

  • R::new使用了方法引用,它是从Java8中出现的,可以让您重用现有的方法定义并像Lambda一样传递它们。因此,当您编写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() 则无法通过,这表明问题并不是很严重,而是一个有趣的巧合。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接