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()
. Ни больше ни меньше.