В лямбда локальные переменные должны быть окончательными, но переменные экземпляра нет. Почему так?
Lambdas: локальные переменные нужны final, переменные экземпляра не
Ответ 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();
}
}
Вывод: Нечетные = истина