Я играю с лямбдами в Java 8, и я наткнулся на предупреждение local variables referenced from a lambda expression must be final or effectively final
. Я знаю, что когда я использую переменные внутри анонимного класса, они должны быть окончательными во внешнем классе, но все же - в чем разница между окончательным и эффективным финалом?
Разница между окончательным и эффективным окончательным
Ответ 1
... начиная с Java SE 8, локальный класс может обращаться к локальным переменным и параметрам заключающего блока, которые являются окончательными или фактически конечными. Переменная или параметр, значение которых никогда не изменяется после его инициализации, фактически является окончательным.
Например, предположим, что переменная numberLength
не объявлена как final, и вы добавили отмеченный оператор присваивания в конструктор PhoneNumber
:
public class OutterClass {
int numberLength; // <== not *final*
class PhoneNumber {
PhoneNumber(String phoneNumber) {
numberLength = 7; // <== assignment to numberLength
String currentNumber = phoneNumber.replaceAll(
regularExpression, "");
if (currentNumber.length() == numberLength)
formattedPhoneNumber = currentNumber;
else
formattedPhoneNumber = null;
}
...
}
...
}
Из-за этого оператора присваивания переменная numberLength больше не является окончательной. В результате компилятор Java генерирует сообщение об ошибке, похожее на "локальные переменные, на которые ссылается внутренний класс, должны быть окончательными или эффективно окончательными", когда внутренний класс PhoneNumber пытается получить доступ к переменной numberLength:
http://codeinventions.blogspot.in/2014/07/difference-between-final-and.html
http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html
Ответ 2
Я считаю, что самый простой способ объяснить "эффективно окончательный" - представить себе добавление final
модификатора в объявление переменной. Если при этом изменении программа продолжает вести себя одинаково, как во время компиляции, так и во время выполнения, эта переменная является фактически окончательной.
Ответ 3
В соответствии с docs:
Переменная или параметр, значение которого никогда не изменяется после его инициализации, фактически является окончательным.
В принципе, если компилятор обнаруживает, что переменная не отображается в назначениях вне ее инициализации, то эта переменная считается эффективной окончательной.
Например, рассмотрим некоторый класс:
public class Foo {
public void baz(int bar) {
// While the next line is commented, bar is effectively final
// and while it is uncommented, the assignment means it is not
// effectively final.
// bar = 2;
}
}
Ответ 4
'Effectively final' - это переменная, которая не выдаст ошибку компилятора, если она будет добавлена 'final'
Из статьи "Брайан Гетц",
Неформально локальная переменная является окончательной, если ее начальное значение никогда не изменяется - иными словами, объявление ее окончательным не приведет к ошибке компиляции.
Ответ 5
Эта переменная ниже final, поэтому мы не можем изменить ее значение после инициализации. Если мы попытаемся получить ошибку компиляции...
final int variable = 123;
Но если мы создадим такую переменную, мы можем изменить ее значение...
int variable = 123;
variable = 456;
Но в Java 8 по умолчанию все переменные final. Но наличие 2-й строки в коде делает ее не окончательной. Поэтому, если мы удалим вторую строку из приведенного выше кода, наша переменная теперь "эффективно окончательная" ...
int variable = 123;
Итак. Любая переменная, назначенная один раз и только один раз, является "фактически окончательной" .
Ответ 6
Когда в выражении лямбда используется назначенная локальная переменная из его вмещающего пространства, существует важное ограничение. В выражении лямбда может использоваться только локальная переменная, значение которой не изменяется. Это ограничение называется " захват переменной", который описывается как; лямбда-выражения, а не переменные.
Локальные переменные, которые могут использовать выражение лямбда, известны как " эффективно окончательный".
Эффективно конечная переменная - это значение, значение которого не изменяется после его первого назначения. Нет необходимости явно объявлять такую переменную как final, хотя делать это не будет ошибкой.
Давайте посмотрим на пример, у нас есть локальная переменная i, которая инициализируется значением 7, причем в выражении лямбда мы пытаемся изменить это значение, назначив новое значение i. Это приведет к ошибке компилятора - "Локальная переменная i, определенная в охватывающей области, должна быть окончательной или фактически окончательной"
@FunctionalInterface
interface IFuncInt {
int func(int num1, int num2);
public String toString();
}
public class LambdaVarDemo {
public static void main(String[] args){
int i = 7;
IFuncInt funcInt = (num1, num2) -> {
i = num1 + num2;
return i;
};
}
}
Ответ 7
Переменная окончательная или эффективно окончательная, когда она инициализируется один раз, и она никогда не мутировала в своем классе владельца, И не может инициализировать его в циклах или внутренних классах.
Финал
final int number;
number = 23;
Эффективно Final:
int number;
number = 34;
Ответ 8
Эффективная окончательная тема описана в JLS 4.12.4, а последний абзац содержит четкое объяснение:
Если переменная является фактически окончательной, добавление окончательного модификатора к ее объявлению не приведет к ошибкам во время компиляции. И наоборот, локальная переменная или параметр, объявленный окончательно в действительной программе, становится окончательно окончательным, если последний модификатор удален.
Ответ 9
public class LambdaScopeTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
// The following statement causes the compiler to generate
// the error "local variables referenced from a lambda expression
// must be final or effectively final" in statement A:
//
// x = 99;
}
}
}
Как говорили другие, переменная или параметр, значение которых никогда не изменяется после его инициализации, является фактически окончательным. В приведенном выше коде, если вы измените значение x
во внутреннем классе FirstLevel
, тогда компилятор выдаст вам сообщение об ошибке:
Локальные переменные, на которые ссылается выражение лямбда, должны быть окончательными или фактически окончательными.
Ответ 10
Если бы вы могли добавить
final
модификатор к локальной переменной, он был бы действительно финальным.
Лямбда-выражения могут получить доступ
-
статические переменные,
-
переменные экземпляра,
-
эффективно окончательные параметры метода, и
-
эффективно финальные локальные переменные.
Дополнительно,
Фактически
effectively final
переменная - это переменная, значение которой никогда не изменяется, но она не объявляется с ключевым словомfinal
.
Источник: Начиная с Java: от структур управления до объектов (6-е издание), Тони Гаддис
Кроме того, не забывайте, что значение final
означает, что он инициализируется ровно один раз перед первым использованием.
Ответ 11
final - объявить переменную с ключевым словом final
, пример:
final double pi = 3.14 ;
это остается final
протяжении всей программы.
фактически окончательный: любая локальная переменная или параметр, которому присваивается значение только один раз (или обновляется только один раз). Это может не оставаться эффективно окончательным в течение всей программы. таким образом, это означает, что фактически окончательная переменная может потерять свое фактически конечное свойство сразу после того, как она получит/обновит по крайней мере еще одно назначение. пример:
class EffectivelyFinal {
public static void main(String[] args) {
calculate(124,53);
}
public static void calculate( int operand1, int operand2){
int rem = 0; // operand1, operand2 and rem are effectively final here
rem = operand1%2 // rem lost its effectively final property here because it gets its second assignment
// operand1, operand2 are still effectively final here
class operators{
void setNum(){
operand1 = operand2%2; // operand1 lost its effectively final property here because it gets its second assignment
}
int add(){
return rem + operand2; // does not compile because rem is not effectively final
}
int multiply(){
return rem * operand1; // does not compile because both rem and operand1 are not effectively final
}
}
}
}
Ответ 12
Объявление переменной final
или отсутствие объявления ее final
, но сохранение ее в качестве final может привести (в зависимости от компилятора) к другому байт-коду.
Давайте посмотрим на небольшой пример:
public static void main(String[] args) {
final boolean i = true; // 6 // final by declaration
boolean j = true; // 7 // effectively final
if (i) { // 9
System.out.println(i);// 10
}
if (!i) { // 12
System.out.println(i);// 13
}
if (j) { // 15
System.out.println(j);// 16
}
if (!j) { // 18
System.out.println(j);// 19
}
}
Соответствующий байт-код main
метода (Java 8u161 в Windows 64 Bit):
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: istore_1
2: iconst_1
3: istore_2
4: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
7: iconst_1
8: invokevirtual #22 // Method java/io/PrintStream.println:(Z)V
11: iload_2
12: ifeq 22
15: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
18: iload_2
19: invokevirtual #22 // Method java/io/PrintStream.println:(Z)V
22: iload_2
23: ifne 33
26: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
29: iload_2
30: invokevirtual #22 // Method java/io/PrintStream.println:(Z)V
33: return
Соответствующая таблица номеров строк:
LineNumberTable:
line 6: 0
line 7: 2
line 10: 4
line 15: 11
line 16: 15
line 18: 22
line 19: 26
line 21: 33
Как мы видим, исходный код в строках 12
, 13
, 14
не появляется в байт-коде. Это потому, что i
true
и не буду менять его состояние. Таким образом, этот код недоступен (подробнее в этом ответе). По той же причине код в строке 9
тоже отсутствует. Состояние i
не нужно оценивать, так как оно точно true
.
С другой стороны, хотя переменная j
является окончательной, она не обрабатывается таким же образом. Нет таких применений оптимизации. Состояние j
оценивается два раза. Байт-код один и тот же, независимо от того, является ли j
окончательным.
Ответ 13
Однако, начиная с Java SE 8, локальный класс может обращаться к локальным переменным и параметрам > охватывающего блока, которые являются окончательными или фактически окончательными.
Это не началось на Java 8, я использую это с давних пор. Этот код использовал (до java 8), чтобы быть законным:
String str = ""; //<-- not accesible from anonymous classes implementation
final String strFin = ""; //<-- accesible
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String ann = str; // <---- error, must be final (IDE gives the hint);
String ann = strFin; // <---- legal;
String str = "legal statement on java 7,"
+"Java 8 doesn't allow this, it thinks that I'm trying to use the str declared before the anonymous impl.";
//we are forced to use another name than str
}
);