Ich spiele mit Lambdas in Java 8 und bin auf die Warnung lokale Variablen, auf die von einem Lambda-Ausdruck verwiesen wird, müssen endgültig oder effektiv endgültig sein
gestoßen. Ich weiß, dass wenn ich Variablen innerhalb einer anonymen Klasse verwende, sie in der äußeren Klasse final sein müssen, aber trotzdem - was ist der Unterschied zwischen final und effektiv endgültig?
Antworten
Zu viele Anzeigen?Wenn Sie das
final
-Schlüsselwort einer lokalen Variablen hinzufügen könnten, war es effektiv final.
Lambda-Ausdrücke können zugreifen
-
auf statische Variablen,
-
Instanzvariablen,
-
effektiv finale Methodenparameter und
-
effektiv finale lokale Variablen.
Zusätzlich,
Eine
effektiv finale
Variable ist eine Variable, deren Wert nie verändert wird, die jedoch nicht mit demfinal
-Schlüsselwort deklariert ist.
Quelle: Starting Out with Java: From Control Structures through Objects (6th Edition), Tony Gaddis
Vergessen Sie außerdem nicht die Bedeutung von final
, dass es genau einmal initialisiert wird, bevor es zum ersten Mal verwendet wird.
Das Deklarieren einer Variablen final
oder das Nicht-Deklarieren als final
, aber sie als effektiv final zu halten, kann (je nach Compiler) zu unterschiedlichem Bytecode führen.
Werfen wir einen Blick auf ein kleines Beispiel:
public static void main(String[] args) {
final boolean i = true; // 6 // final durch Deklaration
boolean j = true; // 7 // effektiv 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 Methode main
(Java 8u161 auf 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
Wie wir sehen, erscheint der Quellcode in den Zeilen 12
, 13
, 14
nicht im Bytecode. Das liegt daran, dass i
true
ist und seinen Zustand nicht ändern wird. Daher ist dieser Code unerreichbar (mehr dazu in dieser Antwort). Aus dem gleichen Grund fehlt auch der Code in Zeile 9
. Der Zustand von i
muss nicht ausgewertet werden, da er sicherlich true
ist.
Andererseits, obwohl die Variable j
effektiv final ist, wird sie nicht auf die gleiche Weise verarbeitet. Es werden keine solchen Optimierungen angewendet. Der Zustand von j
wird zweimal evaluiert. Der Bytecode ist unabhängig davon, ob j
effektiv final ist, der gleiche.
Es gibt drei Arten von finalen Variablen, die in Java deklariert werden.
1. Implizit deklarierte finale Variablen
Es gibt 3 Arten von implizit deklarierten finalen Variablen.
- Felder, die in Schnittstellen deklariert sind.
- Lokale Variablen, die als Ressource in einer try-with-resource-Anweisung deklariert sind.
- Ausnahmeparameter in einem Multi-Try-Block.
2. Explizit deklarierte finale Variablen
Dies sind Variablen, die mit dem final
-Schlüsselwort deklariert sind.
3. Effektiv finale Variablen
Variablen gelten als effektiv final, wenn:
i. Eine lokale Variable in einer Anweisung deklariert wird und deren Deklarator einen Initialisierer hat, ODER eine lokale Variable nach einem Muster deklariert wird und den folgenden Regeln folgen sollte:
- nicht mit
final
deklariert - nie als Wertzuweisung vorkommen
- nie als Operand eines Präfix- oder Postfix-Inkrement- oder Dekrementoperators vorkommen
z.B. Eine lokale Variable in einer Anweisung deklariert wird und deren Deklarator einen Initialisierer hat.
List ls = Arrays.asList("A", "B", "C");
for (Iterator iterator = ls.iterator(); iterator.hasNext();) {
String s = (String) iterator.next();
System.out.println(s);
}
Hier ist die Variable iterator effektiv final, da sie den oben genannten Regeln folgt.
Hinweis: Wenn eine Variable effektiv final ist, kann sie auch mit dem final
-Schlüsselwort deklariert werden.
List ls = Arrays.asList("A", "B", "C");
for (final Iterator iterator = ls.iterator(); iterator.hasNext();) {
String s = (String) iterator.next();
System.out.println(s);
}
z.B. Lokale Variable nach einem Muster deklariert wird.
public String getStringfromObj(Object o){
if (o instanceof String s && s.startsWith("Java")) { // kann als final String s deklariert werden
return s;
}
return "";
}
ii. Eine lokale Variable wird in einer Anweisung deklariert und später initialisiert. Dies sollte den folgenden Regeln folgen.
- nicht mit
final
deklariert - nie als Wertzuweisung vor und nach einer bestimmten Zuweisung vorkommen
- nie als Operand eines Präfix- oder Postfix-Inkrement- oder Dekrementoperators vorkommen
z.B.
List ls = Arrays.asList("A", "B", "C");
int k; // kann als final int k deklariert werden
if ((k = ls.size()) > 0) {
System.out.println(k);
}
iii. Ein Methode, Konstruktor, Lambda oder Ausnahmeparameter, dessen Deklarator einen Initialisierer hat.
Allerdings kann eine lokale Klasse ab Java SE 8 auf lokale Variablen und Parameter des umgebenden Blocks zugreifen, die final oder effektiv final sind.
Dies begann nicht in Java 8, ich benutze das schon lange. Dieser Code war (vor Java 8) legal:
String str = ""; //<-- nicht zugänglich von anonymen Klassenimplementierungen
final String strFin = ""; //<-- zugänglich
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String ann = str; // <---- Fehler, muss final sein (IDE gibt den Hinweis);
String ann = strFin; // <---- legal;
String str = "legale Anweisung in Java 7,"
+"Java 8 erlaubt dies nicht, es denkt, dass ich versuche, den vor der anonymen Implementierung deklarierten str zu verwenden.";
// wir sind gezwungen, einen anderen Namen als str zu verwenden
}
);
- See previous answers
- Weitere Antworten anzeigen