Різниця між остаточним та фактично остаточним


351

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


2
Дуже багато відповідей, але всі по суті означають "немає різниці". Але чи справді це правда? На жаль, я не можу знайти мовну специфікацію для Java 8.
Олександр Дубінський

3
@AleksandrDubinsky docs.oracle.com/javase/specs
ейс

@AleksandrDubinsky не "справді" правда. Я знайшов один виняток із цього правила. Локальна змінна, ініціалізована з константою, не є постійним виразом для компілятора. Ви не можете використовувати таку змінну для випадку в переключенні / випадку, поки явно не додасте остаточне ключове слово. Наприклад, "int k = 1; перемикач (someInt) {випадок k: ...".
Генно Вермеулен

Відповіді:


234

... починаючи з 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


68
+1 Примітка: якщо посилання не змінюється, воно фактично є остаточним, навіть якщо об'єкт, на який посилається, змінюється.
Пітер Лоурі

1
@stanleyerror Це може мати деяку допомогу: stackoverflow.com/questions/4732544 / ...

1
Я думаю , що більш корисно , ніж приклад НЕ ефективно остаточні, є прикладом того , коли що - то є фактично остаточне. Хоча в описі це стає зрозумілим. Var не потрібно оголошувати остаточним, якщо жоден код не змінює його значення.
Скічан

1
Приклад невірний. Цей код ідеально компілюється (без точок, звичайно). Щоб отримати помилку компілятора, цей код повинен знаходитися в якомусь методі, щоб він numberLengthстав локальною змінною цього методу.
микола

1
Чи є причина, чому цей приклад настільки складний? Чому більшість кодів стосується абсолютно нерелевантної операції регулярного виразів? І, як @mykola вже говорив, у ньому повністю відсутня позначка щодо ефективної кінцевої властивості, оскільки це стосується лише локальних змінних і в цьому прикладі немає локальної змінної.
Хольгер

131

Я вважаю, що найпростіший спосіб пояснити "ефективно остаточний" - це уявити додавання finalмодифікатора до заяви змінної. Якщо з цією зміною програма продовжує вести себе так само, як під час компіляції, так і під час виконання, ця змінна фактично є остаточною.


4
Це правда, доки розуміння "фіналу" Java 8 добре зрозуміле. В іншому випадку я подивлюся на змінну, не оголошену остаточною, яку ви призначили пізніше, і помилково думаю, що вона не була остаточною. Можна сказати "звичайно" ... але не всі приділяють стільки уваги останнім змінам мовної версії, скільки повинні.
crazy4jesus

8
Один виняток із цього правила полягає в тому, що локальна змінна, ініціалізована з константою, не є постійним виразом для компілятора. Ви не можете використовувати таку змінну для випадку в переключенні / випадку, поки явно не додасте остаточне ключове слово. Наприклад, "int k = 1; перемикач (someInt) {випадок k: ...".
Генно Вермеулен

2
@HennoVermeulen case-case не є винятком із правила в цій відповіді. Мова визначає, що case kпотрібно: постійний вираз, який може бути постійною змінною ("Постійна змінна - це кінцева змінна примітивного типу або типу String, яка ініціалізується постійним виразом" JLS 4.12.4 ), що є особливим випадком остаточного змінна.
Колін Д Беннетт

3
У моєму прикладі компілятор скаржиться, що k не є постійним виразом, тому його не можна використовувати для комутатора. При додаванні остаточного поведінка компіляції змінюється, оскільки вона є постійною змінною і може використовуватися в комутаторі. Отже, ви праві: правило все-таки правильне. Він просто не поширюється на цей приклад і не каже, чи k фактично є остаточним чи ні.
Генно Вермеулен

36

Згідно з документами :

Змінна або параметр, значення якого ніколи не змінюється після його ініціалізації, фактично є остаточним.

В основному, якщо компілятор знаходить змінну не відображається у призначеннях поза її ініціалізацією, то змінна вважається ефективно остаточною .

Наприклад, розглянемо деякий клас:

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;
    }
}

Документи говорять про локальні змінні. barу вашому прикладі не локальна змінна, а поле. "Ефективно остаточний" у повідомленні про помилку, як зазначено вище, взагалі не стосується полів.
Антті Хаапала

6
@AnttiHaapala bar- це параметр, а не поле.
peter.petrov

30

"Ефективно остаточний" - це змінна, яка не дала б помилки компілятора, якби вона була додана "final"

Зі статті «Брайана Геца»,

Неофіційно локальна змінна фактично є остаточною, якщо її початкове значення ніколи не змінюється - іншими словами, оголошення її остаточною не спричинило б збій компіляції.

лямбда-держава-фінал - Брайан Гец


2
ця відповідь показана як цитата, однак у статті Брайана такого точного тексту немає, напевно, не додане слово . Натомість це цитата: Неофіційно локальна змінна фактично є остаточною, якщо її початкове значення ніколи не змінюється - іншими словами, оголошення її остаточною не спричинить збій компіляції.
lcfd

З дослівної копії статті: Неофіційно локальна змінна фактично є остаточною, якщо її початкове значення ніколи не змінюється - іншими словами, оголошення її остаточною не спричинить збій компіляції.
Ajeet Ganga

26

Ця змінна, наведена нижче, є остаточною , тому ми не можемо змінити її значення після ініціалізації. Якщо ми спробуємо, ми отримаємо помилку компіляції ...

final int variable = 123;

Але якщо ми створимо подібну змінну, ми можемо змінити її значення ...

int variable = 123;
variable = 456;

Але в Java 8 всі змінні за замовчуванням є остаточними . Але наявність 2-го рядка в коді робить його не остаточним . Отже, якщо ми видалимо 2-й рядок із наведеного вище коду, наша змінна тепер "ефективно остаточна" ...

int variable = 123;

Отже .. Будь-яка змінна, яка призначається один раз і лише один раз, є "ефективно остаточною" .


Настільки ж просто, як і відповідь.
superigno

@Eurig, Цитування, необхідне для "всіх змінних за замовчуванням є остаточними".
Пейсьєр

10

Змінна є остаточною або ефективно остаточною, коли вона ініціалізується один раз і ніколи не змінюється у своєму власницькому класі. І ми не можемо ініціалізувати це у циклах чи внутрішніх класах .

Фінал :

final int number;
number = 23;

Ефективно заключний :

int number;
number = 34;

Примітка : Фінальний та ефективний фінал схожі (їх значення не змінюється після присвоєння), але лише те, що ефективні остаточні змінні не оголошуються за допомогою ключового словаfinal .


7

Коли лямбда-вираз використовує призначену локальну змінну зі свого оточуючого простору, існує важливе обмеження. Вираз лямбда може використовувати лише локальну змінну, значення якої не змінюється. Це обмеження називається " змінним захопленням ", яке описується як; значення захоплення вираження лямбда, а не змінні .
Локальні змінні, які може використовувати лямбда-вираз, відомі як " ефективно остаточні ".
Ефективно остаточна змінна - це значення, значення якої не змінюється після її першого призначення. Не потрібно чітко заявляти таку змінну як остаточну, хоча це не буде помилкою.
Подивимось це на прикладі, у нас є локальна змінна 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;
        };
    }   
}

2

Ефективна заключна тема описана в JLS 4.12.4, а останній параграф містить чітке пояснення:

Якщо змінна фактично остаточна, додавання остаточного модифікатора до її декларації не вводитиме жодних помилок часу компіляції. І навпаки, локальна змінна або параметр, який оголошується остаточним у дійсній програмі, стає фактично остаточним, якщо остаточний модифікатор видалений.


2

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
            }
        }   
   }    
}

Це невірно згідно зі специфікацією мови Java: " Кожного разу, коли це відбувається як ліва частина у виразі призначення, воно, безумовно, не призначається та не визначено перед призначенням." Змінна / параметр або завжди, або ніколи фактично не є остаточним. Більш чітко, якщо ви не можете додати finalключове слово до декларації без введення помилок компіляції, то воно фактично не є остаточним . Це контрастне положення цього твердження: "Якщо змінна фактично остаточна, додавання остаточного модифікатора до її декларації не вводитиме жодних помилок часу компіляції".
AndrewF

Коментарі в прикладі коду є невірними з усіх причин, описаних у моєму коментарі. "Ефективно остаточний" - це не стан, який може змінюватися з часом.
AndrewF

@AndrewF, якщо вона не змінюється з часом, як ви думаєте, що останній рядок не складається? rem був фактично остаточним за рядком 1 у методі обчислення. Однак в останньому рядку укладач скаржиться, що
Науковий метод

Ви впевнені, що частину коду потрібно видалити з коду, щоб компілювати, але це не відображає поведінку часу виконання. Під час компіляції ви можете вирішити, чи є змінна ефективно остаточною, чи ні - на основі специфікації, вона або завжди ефективно остаточна, або ніколи фактично не є остаточною. Компілятор може визначити, статично дивлячись, як змінна використовується в усій її області. Властивість не можна отримати або втратити під час роботи програми. Термін чітко визначений специфікацією - ознайомтеся з іншими відповідями, які досить добре пояснюють це.
AndrewF

1
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

Якщо ви могли додати finalмодифікатор до локальної змінної, він фактично був остаточним.

Лямбда-вирази можуть отримати доступ

  • статичні змінні,

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

  • ефективні параметри остаточного методу та

  • ефективно остаточні локальні змінні.

Джерело: OCP: Oracle Certified Professional Java SE 8 Посібник з вивчення програміста, Жанна Боярський, Скотт Селікофф

Крім того,

effectively finalЗмінна є змінною, значення якої ніколи не змінюється, але він не оголошений з finalключовим словом.

Джерело: Починаючи з Java: від структур управління через об’єкти (6-е видання), Тоні Гаддіс

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


0

Оголошення змінної 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 біт):

  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того, чи є фактично остаточним .


Я вважав би це неефективністю компілятора, а не обов'язково такою, яка все-таки буде правдою для нових компіляторів. У ідеальній компіляції, якщо змінна фактично остаточна, вона генерує всі ті ж самі оптимізації, що і одна оголошена фінальна. Тому не покладайтеся на думку про те, що фактично фінал автоматично повільніше, ніж декларування чогось остаточного.
AndrewF

@AndrewF Як правило, ти маєш рацію, поведінка може змінитися. Ось чому я написав " може призвести (залежить від компілятора) в різному байт-коді ". Тільки через відсутність оптимізації (різний байт-код) я б не вважав виконання більш повільним. Але все ж різниця у наведеному випадку.
LuCio

0

Ефективно остаточна змінна - це локальна змінна, яка:

  1. Не визначено як final
  2. Призначається ТІЛЬКИ один раз.

Хоча кінцева змінна є змінною, яка є:

  1. оголошено за допомогою finalключового слова.

-6

Однак, починаючи з 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'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
    }
);

2
Заява посилається на те, що в <Java 8 можна отримати доступ лише до final змінних, а в Java 8 - і до тих, що є фактично остаточними.
Антті Хаапала

Я бачу лише код, який не працює, незалежно від того, використовуєте ви Java 7 або Java 8.
Holger
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.