Разница между final и effectively final

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

11 ответов


... начиная с 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


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


по словам 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;
    }
}

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

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

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


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

final int variable = 123;

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

int variable = 123;
variable = 456;

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

int variable = 123;

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


переменная финал или эффективно окончательной, когда инициализации после и никогда мутировал в своем классе владельца. И мы!--3-->не удалось инициализировать его в петли или внутренние классы.

финал:

final int number;
number = 23;

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

int number;
number = 34;

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

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

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


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

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


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

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

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

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

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

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

источник: OCP: Oracle сертифицированный профессиональный Java SE 8 программист II учебное пособие, Жанна Боярский, Скотт Селикофф

кроме того,

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

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

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


однако, начиная с Java SE 8, локальный класс может получить доступ к локальным переменным и параметрам блока >enclosing, которые являются окончательными или фактически окончательными.

это не началось на 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's 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
    }
);