Почему lambdas в Java 8 запрещает пересылать ссылку на переменные-члены, где нет анонимных классов?

Следующий класс содержит переменную-член runnable, которая инициализируется экземпляром анонимного внутреннего класса. Внутренний класс ссылается на один и тот же элемент:

class Example {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println(runnable);
        }
    };
}

Это не проблема, если метод не выполняется до того, как член был назначен, и JLS разрешает такую ​​ссылку.

Объявление переменной-члена теоретически может быть преобразовано в выражение лямбда следующим образом:

Runnable runnable = () -> System.out.println(runnable);

Насколько я понимаю, это функционально эквивалентно предыдущему примеру, но он отклоняется javac 1.8.0_05 со следующим сообщением об ошибке:

Error:(2, 54) java: self-reference in initializer

Хотя это утверждение верно, я не понимаю, почему это было запрещено. Было ли это намеренно запрещено, может быть, потому, что лямбда-выражения скомпилированы в разные байтовые коды, что может привести к проблемам, если это позволяет? Или просто было запрещено, потому что уже были проблемы с этими ссылками, когда они использовались в анонимных внутренних классах? Или это было непреднамеренно запрещено писателями JLS? Или это ошибка в javac?

Ответ 1

Ошибка JDK-8027941 описывает именно это. Дэн Смит (Project Lambda Specification Lead) пишет, что это не ошибка, и не ограничивается лямбдами.

В комментариях к связанному отчету об ошибках он делает следующее:

8.3.2.3: Во-первых, "использование" поля в инициализаторе поля обычно запрещено, если использование происходит до объявления поля. Спецификация не очень понятна в этом, но всегда было намерением, что "до" включает собственный инициализатор поля. Поэтому "int x = x+1;" не является допустимым объявлением поля.

Он также замечает:

Можно было бы добавить функцию, которая специально обрабатывала бы лямбда-тела, например тела анонимных классов (или, в более общем плане, разрешать лямбда ссылаться на себя, если она является переменным инициализатором), но это еще не сделано. (FWIW, простая настройка 8.3.2.3 не была бы полностью безопасной, так же как четвертая пуля в настоящее время не полностью безопасна: "Function f = (Function) ((Function) e -> f.apply(e)).apply(null);".)

Я думаю, проблема заключается в том, что разработчики Java хотят иметь простые синтаксические правила, чтобы решить, какие утверждения разрешены, а не в зависимости от более сложного анализа семантического кода. Преимущество, вероятно, более простая спецификация и, следовательно, меньшее количество требований к компиляторам, в то время как стоимость заключается в том, что программисты не могут выразить каждую программу - по крайней мере, не так, как они хотят.


Как отмечает Марко Топольник, есть решение: полностью квалифицировать поле. Пример из отчета об ошибке:

import java.util.function.Function; 

public class LambdaSelfRef { 

    // COMPILATION FAILURE 
    public static Function<Object, Object> op1 = e -> op1.apply(e); 

    // COMPILES OK 
    public static Function<Object, Object> op2 = e -> LambdaSelfRef.op2.apply(e); 

    /* ... */
}