Unterschied zwischen endgültig und effektiv endgültig


350

Ich spiele mit Lambdas in Java 8 und bin auf eine Warnung gestoßen local variables referenced from a lambda expression must be final or effectively final. Ich weiß, dass Variablen, die in einer anonymen Klasse verwendet werden, in der äußeren Klasse endgültig sein müssen, aber dennoch - was ist der Unterschied zwischen endgültig und effektiv endgültig ?


2
Viele Antworten, aber alle bedeuten im Wesentlichen "keinen Unterschied". Aber ist das wirklich wahr? Leider kann ich keine Sprachspezifikation für Java 8 finden.
Aleksandr Dubinsky

3
@AleksandrDubinsky docs.oracle.com/javase/specs
uvb

@AleksandrDubinsky nicht "wirklich" wahr. Ich habe eine Ausnahme von dieser Regel gefunden. Eine mit einer Konstante initialisierte lokale Variable ist für den Compiler kein konstanter Ausdruck. Sie können eine solche Variable nicht für einen Fall in einem Schalter / Fall verwenden, bis Sie das endgültige Schlüsselwort explizit hinzugefügt haben. ZB "int k = 1; switch (someInt) {case k: ...".
Henno Vermeulen

Antworten:


233

... ab Java SE 8 kann eine lokale Klasse auf lokale Variablen und Parameter des umschließenden Blocks zugreifen, die endgültig oder effektiv endgültig sind. Eine Variable oder ein Parameter, dessen Wert nach der Initialisierung niemals geändert wird, ist effektiv endgültig.

Angenommen, die Variable numberLengthwird nicht als endgültig deklariert, und Sie fügen die markierte Zuweisungsanweisung im PhoneNumberKonstruktor hinzu:

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

  ...

  }

...

}

Aufgrund dieser Zuweisungsanweisung ist die Variable numberLength nicht mehr endgültig. Infolgedessen generiert der Java-Compiler eine Fehlermeldung ähnlich der Meldung "Lokale Variablen, auf die von einer inneren Klasse verwiesen wird, müssen endgültig oder effektiv endgültig sein", wobei die innere Klasse PhoneNumber versucht, auf die Variable numberLength zuzugreifen:

http://codeinventions.blogspot.in/2014/07/difference-between-final-and.html

http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html


68
+1 Hinweis: Wenn eine Referenz nicht geändert wird, ist sie auch dann endgültig, wenn das referenzierte Objekt geändert wird.
Peter Lawrey

1
@ Stanleyerror Dies könnte hilfreich sein: stackoverflow.com/questions/4732544/…

1
Ich denke , nützlicher als ein Beispiel für nicht effektiv endgültig, ist ein Beispiel , wenn etwas ist effektiv endgültig. Die Beschreibung macht es jedoch klar. Var muss nicht als endgültig deklariert werden, wenn kein Code seinen Wert ändert.
Skychan

1
Das Beispiel ist falsch. Dieser Code wird perfekt kompiliert (natürlich ohne Punkte). Um den Compilerfehler zu erhalten, sollte sich dieser Code in einer Methode befinden, damit er numberLengthzu einer lokalen Variablen dieser Methode wird.
Mykola

1
Gibt es einen Grund, warum dieses Beispiel so kompliziert ist? Warum befasst sich der Großteil des Codes mit einer völlig irrelevanten Regex-Operation? Und wie @mykola bereits sagte, fehlt die Markierung bezüglich der effektiven endgültigen Eigenschaft vollständig , da dies nur für lokale Variablen relevant ist und es in diesem Beispiel keine lokale Variable gibt.
Holger

131

Ich finde, der einfachste Weg, "effektiv endgültig" zu erklären, besteht darin, sich vorzustellen, den finalModifikator einer Variablendeklaration hinzuzufügen . Wenn sich das Programm mit dieser Änderung sowohl zur Kompilierungszeit als auch zur Laufzeit weiterhin auf dieselbe Weise verhält, ist diese Variable effektiv endgültig.


4
Dies ist wahr, solange das Verständnis von Java 8s "final" gut verstanden ist. Andernfalls würde ich mir eine Variable ansehen, die nicht als endgültig deklariert wurde und für die Sie später eine Zuweisung vorgenommen haben, und fälschlicherweise denken, dass sie nicht endgültig ist. Man könnte sagen "natürlich" ... aber nicht jeder achtet so sehr auf die neuesten Änderungen der Sprachversion, wie er sollte.
narr4jesus

8
Eine Ausnahme von dieser Regel besteht darin, dass eine mit einer Konstante initialisierte lokale Variable für den Compiler kein konstanter Ausdruck ist. Sie können eine solche Variable nicht für einen Fall in einem Schalter / Fall verwenden, bis Sie das endgültige Schlüsselwort explizit hinzugefügt haben. ZB "int k = 1; switch (someInt) {case k: ...".
Henno Vermeulen

2
@HennoVermeulen Switch-Case ist keine Ausnahme von der Regel in dieser Antwort. Die Sprache gibt an, case kdass ein konstanter Ausdruck erforderlich ist, der eine konstante Variable sein kann ("Eine konstante Variable ist eine endgültige Variable vom primitiven Typ oder vom Typ String, die mit einem konstanten Ausdruck initialisiert wird" JLS 4.12.4 ). Dies ist ein Sonderfall eines final Variable.
Colin D Bennett

3
In meinem Beispiel beschwert sich der Compiler, dass k kein konstanter Ausdruck ist und daher nicht für den Switch verwendet werden kann. Beim Hinzufügen von final ändert sich das Kompilierungsverhalten, da es jetzt eine konstante Variable ist und im Switch verwendet werden kann. Sie haben also Recht: Die Regel ist immer noch korrekt. Es trifft einfach nicht auf dieses Beispiel zu und sagt nicht, ob k effektiv endgültig ist oder nicht.
Henno Vermeulen

36

Laut den Dokumenten :

Eine Variable oder ein Parameter, dessen Wert nach der Initialisierung niemals geändert wird, ist effektiv endgültig.

Wenn der Compiler feststellt, dass eine Variable nicht in Zuweisungen außerhalb ihrer Initialisierung angezeigt wird, wird die Variable als effektiv endgültig betrachtet .

Betrachten Sie zum Beispiel eine Klasse:

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

Die Dokumente sprechen über lokale Variablen. barIn Ihrem Beispiel handelt es sich nicht um eine lokale Variable, sondern um ein Feld. "Effektiv endgültig" in der Fehlermeldung wie oben gilt überhaupt nicht für Felder.
Antti Haapala

6
@AnttiHaapala barist hier ein Parameter, kein Feld.
Peter.petrov

30

'Effectively final' ist eine Variable, die keinen Compilerfehler ausgibt, wenn sie an 'final' angehängt wird.

Aus einem Artikel von 'Brian Goetz',

Informell ist eine lokale Variable effektiv endgültig, wenn ihr Anfangswert nie geändert wird. Mit anderen Worten, wenn sie als endgültig deklariert wird, führt dies nicht zu einem Kompilierungsfehler.

Lambda-State-Finale-Brian Goetz


2
Diese Antwort wird als Zitat angezeigt, es gibt jedoch keinen so genauen Text in Brians Artikel, sicher nicht das angehängte Wort . Dies ist stattdessen ein Zitat: Informell ist eine lokale Variable effektiv endgültig, wenn ihr Anfangswert nie geändert wird. Mit anderen Worten, wenn sie als endgültig deklariert wird, führt dies nicht zu einem Kompilierungsfehler.
lcfd

Aus dem Artikel wörtliche Kopie: Informell ist eine lokale Variable effektiv endgültig, wenn ihr Anfangswert nie geändert wird - mit anderen Worten, wenn sie als endgültig deklariert wird, würde dies keinen Kompilierungsfehler verursachen.
Ajeet Ganga

26

Diese Variable unten ist endgültig , daher können wir ihren Wert nach der Initialisierung nicht mehr ändern. Wenn wir es versuchen, erhalten wir einen Kompilierungsfehler ...

final int variable = 123;

Aber wenn wir eine Variable wie diese erstellen, können wir ihren Wert ändern ...

int variable = 123;
variable = 456;

In Java 8 sind jedoch standardmäßig alle Variablen endgültig . Das Vorhandensein der zweiten Zeile im Code macht sie jedoch nicht endgültig . Wenn wir also die 2. Zeile aus dem obigen Code entfernen, ist unsere Variable jetzt "effektiv endgültig" ...

int variable = 123;

Also .. Jede Variable, die einmal und nur einmal zugewiesen wird, ist "effektiv endgültig" .


So einfach wie die Antwort sein sollte.
Superigno

@Eurig, Zitat erforderlich für "Alle Variablen sind standardmäßig endgültig".
Pacerier

10

Eine Variable ist endgültig oder effektiv endgültig, wenn sie einmal initialisiert wird und in ihrer Eigentümerklasse niemals mutiert ist . Und wir können es nicht in Schleifen oder inneren Klassen initialisieren .

Finale :

final int number;
number = 23;

Effektiv endgültig :

int number;
number = 34;

Hinweis : Final und Effective Final sind ähnlich (ihr Wert ändert sich nach der Zuweisung nicht), aber nur die effektiven Final-Variablen werden nicht mit Keyword deklariert final.


7

Wenn ein Lambda-Ausdruck eine zugewiesene lokale Variable aus seinem umschließenden Raum verwendet, gibt es eine wichtige Einschränkung. Ein Lambda-Ausdruck darf nur lokale Variablen verwenden, deren Wert sich nicht ändert. Diese Einschränkung wird als " Variablenerfassung " bezeichnet, die beschrieben wird als; Erfassungswerte für Lambda-Ausdrücke, keine Variablen .
Die lokalen Variablen, die ein Lambda-Ausdruck verwenden kann, werden als " effektiv endgültig " bezeichnet.
Eine effektiv endgültige Variable ist eine Variable, deren Wert sich nach der ersten Zuweisung nicht ändert. Es ist nicht erforderlich, eine solche Variable explizit als final zu deklarieren, obwohl dies kein Fehler wäre.
Schauen wir uns ein Beispiel an: Wir haben eine lokale Variable i, die mit dem Wert 7 initialisiert wird. Im Lambda-Ausdruck versuchen wir, diesen Wert zu ändern, indem wir i einen neuen Wert zuweisen. Dies führt zu einem Compilerfehler - " Die in einem umschließenden Bereich definierte lokale Variable i muss endgültig oder effektiv endgültig sein. "

@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

Das effektive letzte Thema ist in JLS 4.12.4 beschrieben und der letzte Absatz enthält eine klare Erklärung:

Wenn eine Variable effektiv endgültig ist, führt das Hinzufügen des endgültigen Modifikators zu ihrer Deklaration zu keinen Fehlern bei der Kompilierung. Umgekehrt wird eine lokale Variable oder ein lokaler Parameter, der in einem gültigen Programm als endgültig deklariert wird, effektiv endgültig, wenn der endgültige Modifikator entfernt wird.


2

final ist eine Variable deklarieren mit Schlüsselwort final, Beispiel:

final double pi = 3.14 ;

es bleibt finalwährend des gesamten Programms.

effektiv endgültig : Jede lokale Variable oder jeder lokale Parameter, dem derzeit nur einmal ein Wert zugewiesen wird (oder der nur einmal aktualisiert wird). Es kann sein, dass es während des gesamten Programms nicht effektiv endgültig bleibt . Dies bedeutet also, dass die effektiv endgültige Variable ihre effektiv endgültige Eigenschaft verlieren kann, sobald sie mindestens eine weitere Zuweisung zugewiesen / aktualisiert hat. Beispiel:

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

Dies ist gemäß der Java-Sprachspezifikation falsch: " Immer wenn es als linke Seite in einem Zuweisungsausdruck vorkommt, ist es definitiv nicht zugewiesen und wird vor der Zuweisung nicht definitiv zugewiesen." Eine Variable / ein Parameter ist entweder immer oder nie effektiv endgültig. Wenn Sie das finalSchlüsselwort nicht zu einer Deklaration hinzufügen können, ohne Kompilierungsfehler einzuführen, ist es nicht effektiv endgültig . Dies ist das Gegenteil dieser Aussage: "Wenn eine Variable tatsächlich endgültig ist, führt das Hinzufügen des endgültigen Modifikators zu ihrer Deklaration zu keinen Fehlern bei der Kompilierung."
AndrewF

Die Kommentare im Beispielcode sind aus allen in meinem Kommentar beschriebenen Gründen falsch. "Effektiv endgültig" ist kein Zustand, der sich im Laufe der Zeit ändern kann.
AndrewF

@AndrewF Wenn es sich im Laufe der Zeit nicht ändert, was denkst du, wird die letzte Zeile nicht kompiliert? rem war in Zeile 1 der Berechnungsmethode effektiv endgültig. In der letzten Zeile beschwert sich der Compiler jedoch, dass rem nicht effektiv endgültig ist
The Scientific Method

Sie haben Recht, dass ein Teil des Codes zum Kompilieren aus Ihrem Codeblock entfernt werden muss, dies spiegelt jedoch nicht das Laufzeitverhalten wider. Zum Zeitpunkt der Kompilierung können Sie entscheiden, ob eine Variable effektiv endgültig ist oder nicht - basierend auf der Spezifikation ist sie entweder immer effektiv endgültig oder niemals effektiv endgültig. Der Compiler kann anhand der statischen Betrachtung der Verwendung der Variablen in ihrem gesamten Gültigkeitsbereich feststellen. Die Eigenschaft kann während des Programmablaufs nicht gewonnen oder verloren werden. Der Begriff ist durch die Spezifikation gut definiert - lesen Sie die anderen Antworten, die ihn ziemlich gut erklären.
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; 

        }
    }    
}

Wie andere gesagt haben, ist eine Variable oder ein Parameter, dessen Wert nach der Initialisierung niemals geändert wird, effektiv endgültig. Wenn Sie im obigen Code den Wert von xin der inneren Klasse ändern, gibt FirstLevelder Compiler die folgende Fehlermeldung aus:

Lokale Variablen, auf die aus einem Lambda-Ausdruck verwiesen wird, müssen endgültig oder effektiv endgültig sein.


1

Wenn Sie den finalModifikator einer lokalen Variablen hinzufügen konnten , war er effektiv endgültig.

Lambda-Ausdrücke können darauf zugreifen

  • statische Variablen,

  • Instanzvariablen,

  • effektiv endgültige Methodenparameter und

  • effektiv endgültige lokale Variablen.

Quelle: OCP: Oracle Certified Professional Java SE 8 Programmer II-Studienhandbuch, Jeanne Boyarsky, Scott Selikoff

Zusätzlich,

Eine effectively finalVariable ist eine Variable, deren Wert niemals geändert wird, die jedoch nicht mit dem finalSchlüsselwort deklariert wird.

Quelle: Beginnend mit Java: Von Kontrollstrukturen über Objekte (6. Ausgabe), Tony Gaddis

Vergessen Sie außerdem nicht, finaldass es genau einmal initialisiert wird, bevor es zum ersten Mal verwendet wird.


0

Wenn Sie eine Variable finaldeklarieren oder nicht deklarieren, sie finaljedoch endgültig halten, kann dies (abhängig vom Compiler) zu einem anderen Bytecode führen.

Schauen wir uns ein kleines Beispiel an:

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

Der entsprechende Bytecode der mainMethode (Java 8u161 unter 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

Die entsprechende Zeilennummertabelle:

 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

Da wir den Quellcode an den Linien zu sehen 12, 13, 14erscheint nicht in dem Byte - Code. Das iliegt truedaran, dass es seinen Zustand ändert und nicht ändern wird. Daher ist dieser Code nicht erreichbar (mehr in dieser Antwort ). Aus dem gleichen Grund 9fehlt auch der Code in der Zeile . Der Zustand von imuss nicht ausgewertet werden, da dies truesicher ist.

Auf der anderen Seite ist die Variable jzwar endgültig, wird aber nicht auf die gleiche Weise verarbeitet. Es werden keine derartigen Optimierungen angewendet. Der Zustand von jwird zweimal ausgewertet. Der Bytecode ist derselbe, unabhängig davon j, ob er tatsächlich endgültig ist .


Ich würde dies als eine Ineffizienz des Compilers betrachten und nicht unbedingt als eine, die bei neueren Compilern immer noch zutrifft. Wenn eine Variable in einer perfekten Kompilierung effektiv endgültig ist, generiert sie genau dieselben Optimierungen wie eine deklarierte endgültige. Verlassen Sie sich also nicht auf die Vorstellung, dass effektiv endgültig automatisch langsamer ist, als etwas endgültig zu erklären.
AndrewF

@AndrewF Im Allgemeinen haben Sie Recht, das Verhalten kann sich ändern. Deshalb habe ich geschrieben " kann (abhängig vom Compiler) zu unterschiedlichem Bytecode führen ". Nur wegen der fehlenden Optimierung (anderer Bytecode) würde ich nicht davon ausgehen, dass die Ausführung langsamer ist. Aber es ist immer noch ein Unterschied in dem gezeigten Fall.
LuCio

-6

Ab Java SE 8 kann eine lokale Klasse jedoch auf lokale Variablen und Parameter des> umschließenden Blocks zugreifen, die endgültig oder effektiv endgültig sind.

Dies begann nicht mit Java 8, ich benutze dies seit langer Zeit. Dieser Code wurde (vor Java 8) verwendet, um legal zu sein:

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
Die Anweisung bezieht sich auf die Tatsache, dass in <Java 8 nur auf final Variablen zugegriffen werden kann, in Java 8 jedoch auch auf diejenigen, die effektiv endgültig sind.
Antti Haapala

Ich sehe nur Code, der nicht funktioniert, unabhängig davon, ob Sie Java 7 oder Java 8 verwenden.
Holger
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.