Почему я не могу назначить ссылку на метод непосредственно переменной типа 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 используется. Без этой информации компилятор не может корректно и однозначно разрешать выражение лямбда.