Почему лямбды в Java 8 запрещают прямую ссылку на переменные-члены, где анонимные классы этого не делают?

#lambda #java-8 #javac

#лямбда #java-8 #javac

Вопрос:

Следующий класс содержит переменную-член 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. В eclipse это работает: Runnable runnable = () -> System.out.println(this.runnable); Это происходит только без добавленного this квалификатора.

Ответ №1:

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

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

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

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

Можно было бы добавить функцию, которая обрабатывала бы лямбда-тела особым образом, например, тела анонимных классов (или, в более общем плане, позволяла лямбде ссылаться на себя, если это инициализатор переменной), но этого не было сделано. (FWIW, простая настройка 8.3.2.3 была бы не совсем безопасной, точно так же, как 4-й пункт в настоящее время не совсем безопасен: « 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); 

    /* ... */
}