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