Подтвердить что ты не робот

Lambdas: локальные переменные нужны final, переменные экземпляра не

В лямбда локальные переменные должны быть окончательными, но переменные экземпляра нет. Почему так?

4b9b3361

Ответ 1

Фундаментальное различие между полем и локальной переменной состоит в том, что локальная переменная скопирована, когда JVM создает экземпляр лямбды. С другой стороны, поля могут быть изменены свободно, потому что изменения к ним распространяются также на экземпляр внешнего класса (их область действия - весь внешний класс, как показано Борисом ниже).

Самый простой способ мышления об анонимных классах, закрытиях и labmdas - с точки зрения переменных областей; представьте, что конструктор копирования добавлен для всех локальных переменных, которые вы передаете в закрытие.

Ответ 2

В документе проекта lambda: Состояние Lambda v4

В разделе 7. Переменная захвата. Упоминается, что....

Мы намерены запретить захват изменяемых локальных переменных. Причина в том, что идиомы:

int sum = 0;
list.forEach(e -> { sum += e.size(); });

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

Изменить:

Еще одно замечание: локальные переменные передаются в конструкторе внутреннего класса, когда вы обращаетесь к ним внутри вашего внутреннего класса, и это не будет работать с не конечной переменной, так как значение не конечных переменных может быть изменено после построения.

В то время как в случае переменной экземпляра компилятор передает ссылку класса и класса, ссылка будет использоваться для доступа к переменной экземпляра. Поэтому в случае переменных экземпляра это не требуется.

PS: Следует отметить, что анонимные классы могут получить доступ только к конечным локальным переменным (в JAVA SE 7), тогда как в Java SE 8 вы можете эффективно использовать конечные переменные также внутри лямбда, а также для внутренних классов.

Ответ 3

Похоже, вы спрашиваете о переменных, которые вы можете ссылаться на тело лямбда.

Из JLS §15.27.2

Любая локальная переменная, формальный параметр или параметр исключения, используемые, но не объявленные в выражении лямбда, должны быть объявлены окончательными или быть фактически окончательными (§4.12.4), или ошибка времени компиляции возникает при попытке использования.

Поэтому вам не нужно объявлять переменные как final, вам просто нужно убедиться, что они "эффективно окончательны". Это то же правило, что и для анонимных классов.

Ответ 4

Поскольку переменные экземпляра всегда доступны через операцию доступа к полю при ссылке на какой-либо объект, т.е. some_expression.instance_variable. Даже если вы явно не обращаетесь к нему через точечную нотацию, например instance_variable, она неявно рассматривается как this.instance_variable (или если вы находитесь во внутреннем классе, обращаясь к переменной экземпляра внешнего класса, OuterClass.this.instance_variable, которая находится под капюшон this.<hidden reference to outer this>.instance_variable).

Таким образом, переменная экземпляра никогда не получает прямого доступа, и реальная "переменная", к которой вы напрямую обращаетесь, - это this (которая является "фактически окончательной", поскольку она не может быть назначена) или переменная в начале какого-либо другого выражение.

Ответ 5

В Java 8 в действии эта ситуация объясняется следующим образом:

Возможно, вы спрашиваете себя, почему локальные переменные имеют эти ограничения. Во-первых, theres ключ разница в том, как экземпляры и локальные переменные реализуются за кулисами. Пример переменные хранятся в куче, а локальные переменные - в стеке. Если лямбда могла доступ к локальной переменной напрямую и лямбда были использованы в потоке, затем поток с использованием lambda может попытаться получить доступ к переменной после того, как поток, который присвоил переменную, освободил его. Следовательно, Java реализует доступ к свободной локальной переменной в качестве доступа к ее копии а не доступа к исходной переменной. Это не имеет значения, если локальная переменная присваивается только один раз, следовательно, ограничение. Во-вторых, это ограничение также препятствует типичным императивным шаблонам программирования (которые, поскольку мы объясните в последующих главах, предотвратите легкую параллелизацию), которые мутируют внешнюю переменную.

Ответ 6

В выражениях Lambda вы можете эффективно использовать конечные переменные из окружающей области. Эффективно означает, что необязательно объявлять переменную final, но убедитесь, что вы не изменяете ее состояние в выражении лямбда.

Вы также можете использовать это в закрытии, а использование "this" означает объект-объект, но не сам лямбда, поскольку закрытие - это анонимные функции, и у них нет связанного с ними класса.

Итак, когда вы используете какое-либо поле (допустим, private Integer i;) из класса-оболочки, который не объявлен окончательным и неэффективно окончательный, он все равно будет работать, когда компилятор сделает трюк от вашего имени и вставляет "this" (это .i).

private Integer i = 0;
public  void process(){
    Consumer<Integer> c = (i)-> System.out.println(++this.i);
    c.accept(i);
}

Ответ 7

Вот пример кода, так как я тоже этого не ожидал, я ожидал, что не смогу изменить что-либо за пределами лямбда

 public class LambdaNonFinalExample {
    static boolean odd = false;

    public static void main(String[] args) throws Exception {
       //boolean odd = false; - If declared inside the method then I get the expected "Effectively Final" compile error
       runLambda(() -> odd = true);
       System.out.println("Odd=" + odd);
    }

    public static void runLambda(Callable c) throws Exception {
       c.call();
    }

 }

Вывод:    Нечетные = истина