Почему я не могу назначить ссылку на метод непосредственно переменной типа Object?
Простой вопрос о синтаксисе java-8
. Почему JLS-8
ограничивает такие выражения, как:
Object of_ref = Stream::of; // compile-time error
и разрешить только что-то вроде:
java.util.function.Function of_ref = Stream::of;
Object obj = of_ref; // compiles ok
?
Ответы
Ответ 1
Это потому, что целевой тип ссылки на метод или выражение лямбда должны быть функциональным интерфейсом. Исходя из этого, среда выполнения создаст экземпляр класса, обеспечивающего реализацию данного функционального интерфейса. Подумайте о лямбдах или методах в качестве понятия abstract
. Присвоение его функциональному типу интерфейса дает конкретное значение.
Кроме того, конкретная ссылка лямбда или метода может иметь несколько функциональных интерфейсов в качестве целевого типа. Например, рассмотрим следующую lamda:
int x = 5;
FunctionalInterface func = (x) -> System.out.println(x);
Эта лямбда является Consumer
of x
. В дополнение к этому, любой интерфейс с единственным абстрактным методом со следующей сигнатурой:
public abstract void xxx(int value);
может использоваться как целевой тип. Итак, какой интерфейс вы бы хотели реализовать во время выполнения, если вы назначили тип лямбда Object
? Вот почему вы должны явно предоставлять функциональный интерфейс в качестве целевого типа.
Теперь, когда у вас есть ссылка на функциональный интерфейс, содержащий экземпляр, вы можете назначить его любой супер-ссылке (включая Object
)
Ответ 2
Object
не является функциональным интерфейсом, и ссылка на метод может быть назначена только функциональному интерфейсу. См. Например JLS # 15.13.2
Ссылочное выражение метода совместимо в контексте назначения, контексте вызова или контексте каста с целевым типом T, если T - тип функционального интерфейса (§9.8), и выражение соответствует конгруэнтному типу функции основного целевого типа полученное из T.
Ответ 3
Ключевым моментом является то, что в Java нет "типов функций". Выражение лямбда не имеет "типа" само по себе - его можно ввести в любой функциональный интерфейс, единственная подпись метода которого соответствует лямбда. Следовательно, лямбда-тип основан на типе, предоставляемом его контекстом. Вы должны предоставить функциональный интерфейс в качестве контекста для его получения.
Поучительно рассматривать ту же проблему, но для анонимных классов. Хотя существуют различия в реализации между lambdas и анонимными классами, семантически, lambdas по существу эквивалентны подмножеству анонимных классов, а выражение лямбда всегда может быть преобразовано в эквивалентное выражение для создания анонимного класса.
Когда вы пишете:
Function<T, Stream<T>> of_ref = Stream::of;
он эквивалентен чему-то вроде использования анонимных классов:
Function<T, Stream<T>> of_ref = new Function<T, Stream<T>>() {
Stream<T> apply(T t) {
return Stream.of(t);
}
};
Теперь рассмотрим
Object of_ref = Stream::of;
что эквивалентно анонимным классам?
Object of_ref = new [**What goes here?**]() {
[**What method signature goes here?**] {
return Stream.of(t);
}
};
Вы понимаете, почему это не имеет смысла - мы не знаем, какой тип использовать в качестве базового класса анонимного класса.
Ответ 4
Я подозреваю, что это чисто академический вопрос, так как я не вижу реального случая использования для этого. Тем не менее, я уверен, что это связано с Stream::of
выражением лямбда. Вы также не могли бы этого сделать:
Object of_ref = list -> Stream.of(list);
Я предполагаю, что точный тип возврата сообщает компилятору, что FunctionalInterface используется. Без этой информации компилятор не может корректно и однозначно разрешать выражение лямбда.