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

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

4

5 回答 5

57

您的run方法需要一个Runnable实例,这解释了为什么run(new R())R实现一起使用。

R::new不等于new R()。它可以符合 a Supplier<Runnable>(或类似功能接口)的签名,但R::new不能用作Runnable您的R类的实现。

run可以采用的方法版本R::new可能如下所示(但这会不必要地复杂):

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);

但是,你可以注意到,Runnablecreated withR::new只是调用new R()了它的run方法体。


有效地使用方法引用来执行R#run可以使用实例,如下所示(但在这种情况下,您肯定更愿意r直接使用实例):

R r = new R();
new ConstructorRefVsNew().run(r::run);
于 2019-01-07T10:18:34.507 回答
24

第一个例子:

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

或多或少等同于:

new ConstructorRefVsNew().run( () -> {new R();} );

效果是您只是创建了 R 的一个实例,但不调用它的run方法。

于 2019-01-07T10:23:11.840 回答
10

比较两个调用:

((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 { ... }
}
于 2019-01-07T10:28:53.177 回答
7

run方法需要一个Runnable.

最简单的情况是new R()。在这种情况下,您知道结果是类型的对象RR它本身是一个可运行的,它有一个run方法,这就是 Java 的看法。

但是,当您通过时,R::new正在发生其他事情。您告诉它的是创建一个匿名对象,该对象与一个Runnable谁的run方法运行您传递给它的操作兼容。

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

new Runnable() {

     public void run() {
         new R();
     }
}

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

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

于 2019-01-07T10:24:55.473 回答
0

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

这是什么

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

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

这个怎么运作

因为new R(),很容易理解发生了什么,我将不加解释地离开。

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

在看起来像这样RunnableJDK11

@FunctionalInterface
public interface Runnable {

    public abstract void run();
}

虽然 lambda() -> new R()与方法签名兼容 - 不接受也不返回任何内容,因此代码有效,在这种情况下,传入参数的 runTime 对象如下所示:

Runnable instance with run method {

    public void run() {
      new R();
    };
}

现在我们弄明白了为什么在这种情况下,只有构造R触发了。

更重要的是

我们可以run(new R())像这样实现 lambda 的功能:

new ConstructorRefVsNew().run(() -> System.out.println("R.run runs"));

我终于意识到给定的 lambdaRunnable::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()不能通过,这表明问题不是什么大问题,而是一个有趣的巧合。

于 2020-03-17T12:08:50.927 回答