it-swarm.com.ru

Разница между окончательным и фактически окончательным

Я играю с лямбдами в Java 8, и я наткнулся на предупреждение local variables referenced from a lambda expression must be final or effectively final. Я знаю, что когда я использую переменные внутри анонимного класса, они должны быть финальными во внешнем классе, но все же - в чем разница между final и эффективно final ?

292
alex

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

Например, предположим, что переменная numberLength не объявлена ​​как финальная, и вы добавили помеченный оператор присваивания в конструктор 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

199
Suresh Atta

Я считаю, что самый простой способ объяснить «эффективно финальный» - это представить добавление модификатора final к объявлению переменной. Если с этим изменением программа продолжает вести себя одинаково, как во время компиляции, так и во время выполнения, то эта переменная будет фактически финальной.

115
Maurice Naftalin

Согласно документам :

Переменная или параметр, значение которых никогда не изменяется после его инициализации, фактически является окончательным.

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

Например, рассмотрим некоторый класс:

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;
    }
}
32
Mark Elliot

Из статьи Брайана Гетца,

'Effectively final' - это переменная, которая не выдаст ошибку компилятора если бы это было добавлено 'final'

лямбда-государство-финал- Брайан Гетц

25
Ajeet Ganga

Эта переменная ниже final, поэтому мы не можем изменить ее значение после инициализации. Если мы попытаемся, мы получим ошибку компиляции ...

final int variable = 123;

Но если мы создадим такую ​​переменную, мы сможем изменить ее значение ...

int variable = 123;
variable = 456;

Но в Java 8 все переменные по умолчанию являются final. Но наличие 2-й строки в коде делает его non-final. Поэтому, если мы удалим 2-ю строку из приведенного выше кода, наша переменная будет теперь "эффективно финальной" ...

int variable = 123;

Итак ... Любая переменная, которая назначается один раз и только один раз, является "эффективно финальной".

14
Eurig Jones

Когда лямбда-выражение использует назначенную локальную переменную из окружающего пространства, существует важное ограничение. Лямбда-выражение может использовать только локальную переменную, значение которой не изменяется. Это ограничение называется « переменная перехват », которая описывается как; лямбда-выражения захватывают значения, а не переменные
Локальные переменные, которые может использовать лямбда-выражение, известны как " эффективно окончательно ". 
Фактически последняя переменная - это переменная, значение которой не изменяется после первого присваивания. Нет необходимости явно объявлять такую ​​переменную как 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;
        };
    }   
}
6
infoj

Переменная является final или эффективно final когда она ​​инициализируется один раз и никогда не мутирует в своем классе владельца. И мы не можем инициализировать it в циклы или внутренние классы .

Финал :

final int number;
number = 23;

Эффективно Окончательный :

int number;
number = 34;
6
samadadi

Эффективная последняя тема описана в JLS 4.12.4 и последний абзац содержит четкое объяснение:

Если переменная является окончательной, то добавление модификатора final к ее объявлению не приведет к ошибкам во время компиляции. И наоборот, локальная переменная или параметр, который объявлен окончательным в допустимой программе, становится фактически конечным, если модификатор final удаляется.

2
Dmitry N.
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, то компилятор выдаст вам сообщение об ошибке:

Локальные переменные, на которые ссылается лямбда-выражение, должны быть окончательными или эффективно конечными.

1
Girish

final является объявлением переменной с ключом Word 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
            }
        }   
   }    
}
1
The Scientific Method

Если бы вы могли добавить модификатор final в локальную переменную, это был бы эффективно окончательно.

Лямбда-выражения могут получить доступ 

  • статические переменные, 

  • переменные экземпляра,

  • эффективно окончательные параметры метода .__, и 

  • эффективно финальные локальные переменные.

Источник: OCP: Oracle Certified Professional Java SE 8 Programmer II Учебное пособие, Жанна Боярски, Скотт Селикофф

Дополнительно, 

Переменная effectively final - это переменная, значение которой никогда не равно изменен, но не объявлен с ключевым словом final.

Источник: Начиная с Java: от структур управления до объектов (6-е издание), Тони Гаддис

Кроме того, не забывайте о значении final, что он инициализируется ровно один раз перед первым использованием.

1
snr

Объявление переменной 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 является эффективно final, она не обрабатывается таким же образом. Нет таких применений оптимизации. Состояние j оценивается два раза. Байт-код один и тот же, независимо от того, j является эффективно финальным.

0
LuCio