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

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

4 ответа

Ваш 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);

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

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

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

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

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

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

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

run метод ожидает Runnable,

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

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

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

new Runnable() {

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

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

R::newпри вызове звонит new R(), Ни больше ни меньше.

Просто мои два цента здесь, чтобы дать более читаемый ответ, поскольку люди плохо знакомы с миром java lambda.

Что это

  • R::new использует method reference, который появился из java8, позволяет повторно использовать существующие определения методов и передавать их так же, как лямбды. Итак, когда вы пишетеRunnable::new, на самом деле это означает () -> new R(), результат - лямбда;
  • new R() вызывает конструктор класса R, и вернуть экземпляр этого класса, результатом будет экземпляр R.

Теперь нам ясно, что это такое, но как они работают (почему они компилируемы)?

Как это устроено

За new R(), понять, что произошло, очень легко и я уйду без объяснения причин.

За Runnable::new что означает () -> new R(), нам нужно знать, что Runnable это то, что мы назвали FunctionalInterface, а функциональный интерфейс - это интерфейс только с одним методом, когда мы передаем лямбда методу, который принимает функциональный интерфейс в качестве параметра, лямбда должна соответствовать сигнатуре этого интерфейса, а действие в лямбда заполняется до тела этого метод.

В Runnable в JDK11 выглядит так:

@FunctionalInterface
public interface Runnable {

    public abstract void run();
}

а лямбда () -> new R() совместим с сигнатурой метода - ничего не принимать и ничего не возвращать, поэтому код работает, и в этом случае объект runTime переданного параметра выглядит так:

Runnable instance with run method {

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

теперь мы узнаем, почему в этой ситуации только построение R срабатывает.

Более того

Мы можем добиться того, что run(new R()) делает с лямбдой вот так:

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 все еще хорошо работает, пока new R() не может быть пропущено, что указывает на то, что вопрос не имеет большого значения, а является интересным совпадением.

Другие вопросы по тегам