Runnable :: новый против нового Runnable()

Почему не работает первый из следующих примеров?

  • 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, он возвращает лямбду (которая не является объектом):

Но тогда как это возможно, что пример успешно скомпилирован?

Ответ 1

Ваш метод run использует экземпляр Runnable, и это объясняет, почему run(new R()) работает с реализацией R.

R::new не эквивалентен new R(). Он может соответствовать сигнатуре Supplier<Runnable> (или аналогичных функциональных интерфейсов), но R::new не может использоваться как Runnable, реализованный с вашим классом R.

Версия вашего метода run, который может принимать R::new, может выглядеть так (но это будет излишне сложно):

void run(Supplier<Runnable> r) {
    r.get().run();
}

Почему он компилируется?

Потому что компилятор может сделать Runnable из вызова конструктора, и это будет эквивалентно этой версии лямбда-выражения:

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 напрямую):

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

Ответ 2

Первый пример:

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

более или менее эквивалентно:

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

В результате вы просто создаете экземпляр R, но не вызываете его метод run.

Ответ 3

Сравните два звонка:

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

С помощью ((Runnable)() → new R()) или ((Runnable) R::new) вы создаете новый Runnable который ничего не делает 1.

С помощью new R() вы создаете экземпляр класса R котором метод run четко определен.


1 На самом деле, он создает объект R который не влияет на выполнение.


Я думал о том, чтобы идентично обрабатывать 2 вызова без изменения main метода. Нам потребуется перегрузить 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 { ... }
}

Ответ 4

Метод run ожидает Runnable.

Простой случай - это new R(). В этом случае вы знаете, что результатом является объект типа R R сам по себе является runnable, у него есть метод run, и именно так Java его видит.

Но когда вы передаете R::new что-то еще. Вы говорите, что создаете анонимный объект, совместимый с Runnable чей метод run выполняет операцию, которую вы передали.

Переданная вами операция не является методом R run. Операция является конструктором R Таким образом, как будто вы передали ему анонимный класс, например:

new Runnable() {

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

(Не все детали одинаковы, но это наиболее близкая "классическая" конструкция Java).

R::new, когда вызывается, вызывает new R(). Ни больше ни меньше.