Java final-Schlüsselwort
Java-Variablen konstant machen, Methoden-Überschreibung verhindern und Klassenvererbung mit dem final-Schlüsselwort unterbinden.
final bedeutet „einmal setzen, dann nicht mehr ändern." Es gilt für vier Dinge, und die Wirkung hängt davon ab, worauf es angewendet wird:
- Auf eine Variable kann die Bindung nicht neu zugewiesen werden.
- Auf eine Methode kann die Methode nicht von einer Unterklasse überschrieben werden.
- Auf eine Klasse kann die Klasse nicht erweitert werden.
- Auf einen Parameter kann der Parameter innerhalb der Methode nicht neu zugewiesen werden.
Der gemeinsame Nenner ist „keine weitere Änderung nach der ersten Bindung." Was genau verboten wird, hängt davon ab, was markiert ist.
Lokale final-Variablen
Die einfachste Form: Eine lokale final-Variable kann nach ihrer ersten Zuweisung nicht neu zugewiesen werden.
void demo() {
final int max = 100;
max = 50; // ERROR: cannot assign a value to final variable max
}Die Initialisierung muss nicht bei der Deklaration erfolgen — die erste Zuweisung kann aufgeschoben werden, solange sie genau einmal vor jedem Lesezugriff erfolgt:
final int x;
if (condition) x = 1;
else x = 2;
System.out.println(x); // ok — x was definitely assignedfinal bei einer lokalen Variable ist hauptsächlich ein Dokumentationswerkzeug — es signalisiert einem späteren Leser (und dem Compiler), dass sich dieser Wert ab diesem Punkt nicht mehr ändert. Lambda- und Inner-Class-Captures erfordern es: Jede lokale Variable, die darin verwendet wird, muss final oder effektiv final (nie neu zugewiesen) sein.
Final-Parameter
Ein final-Parameter kann im Methodenrumpf nicht neu zugewiesen werden:
void greet(final String name) {
name = name.toUpperCase(); // ERROR
}Manche Teams fordern final bei jedem Parameter; andere empfinden es als visuellen Lärm. Beides ist in Ordnung — entscheidend ist Konsistenz innerhalb eines Codebasis. Die Gewohnheit, Parameter zu mutieren, die es entmutigt, ist eine echte Fehlerquelle.
Final-Felder
Ein final-Feld wird genau einmal zugewiesen — entweder bei der Deklaration, in einem Instanz-Initialisierer oder im Konstruktor — und danach nie wieder:
public class Point {
private final int x, y;
public Point(int x, int y) {
this.x = x; // assigned once
this.y = y;
}
// no setX or setY — there's no way to change the values
}Das ist der Kern einer unveränderlichen Klasse. Jedes Feld ist final; nichts kann das Objekt nach der Konstruktion mutieren.
Der Compiler prüft, dass jedes final-Feld auf jedem Konstruktorpfad genau einmal zugewiesen wird. Fehlt ein Pfad, gibt es einen Kompilierfehler. Wird zweimal zugewiesen, gibt es ebenfalls einen Kompilierfehler.
static final — Konstanten
Die Standardform für eine Konstante ist public static final mit einem UPPER_SNAKE-Namen:
public static final double PI = 3.141592653589793;
public static final int MAX_RETRY = 3;Statisch, weil kein Grund besteht, eine Kopie pro Instanz zu halten; final, weil es eine Konstante ist. Bei primitiven und String-Konstanten inliniert der Compiler den Wert an jeder Aufrufstelle. Eine Konsequenz: Wenn der Wert einer solchen Konstante geändert wird, behalten Klassen, die darauf verwiesen haben, den alten Wert, bis sie neu kompiliert werden — das Literal wurde zum Zeitpunkt ihrer eigenen Kompilierung in ihren Bytecode eingebettet.
final und Referenzen
Ein häufiges Missverständnis: final friert die Referenz ein, nicht das Objekt, auf das sie zeigt. Bei einem finalen Array oder einer finalen Liste können die Inhalte trotzdem mutiert werden:
final int[] nums = {1, 2, 3};
nums[0] = 99; // ok — mutating the array, not reassigning the reference
nums = new int[5]; // ERROR — reassigning the reference
final List<String> names = new ArrayList<>();
names.add("Rex"); // ok — mutates the list
names = List.of(); // ERRORWenn auch die Inhalte einer Liste eingefroren sein sollen, kann sie mit List.copyOf(...) oder Collections.unmodifiableList(...) umwickelt werden.
Final-Methoden
final bei einer Methode bedeutet, dass keine Unterklasse sie überschreiben darf:
public class Shape {
public final String describe() { // can never be overridden
return getClass().getSimpleName() + " area=" + area();
}
public double area() { return 0; }
}
public class Circle extends Shape {
public String describe() { return "circle"; } // ERROR
}Eine Methode wird mit final markiert, wenn das Überschreiben Invarianten brechen würde, auf die die Klasse angewiesen ist. Das ist eine bewusste Designentscheidung — die meisten Methoden sind in echten Codebasen nicht final — aber für Templates, die sich nicht ändern dürfen, ist es das richtige Mittel.
Final-Klassen
final bei einer Klasse bedeutet, dass keine Klasse sie erweitern darf:
public final class Money { ... }
public class CryptoMoney extends Money { } // ERRORDies ist sinnvoll, wenn Erweiterung das Design zunichte machen würde. Die Standardbibliothek macht das routinemäßig: String, Integer, Long, Double und die übrigen primitiven Wrapper sind alle final — ihre Erweiterung würde ermöglichen, veränderbares Verhalten in einen Typ einzuschmuggeln, den die Sprache als unveränderlich behandelt.
final ist keine Unveränderlichkeit
Ein final-Feld allein macht ein Objekt nicht unveränderlich; es verhindert lediglich, dass die Referenz geändert wird. Echte Unveränderlichkeit erfordert:
- Jedes Feld
final. - Die Klasse selbst
final, oder sorgfältig konstruiert, um zu verhindern, dass Unterklassen veränderbaren Zustand hinzufügen. - Keine Methode, die ein veränderbares internes Objekt preisgibt (defensive Kopien beim Herausgeben).
- Keine Methode, die einem Aufrufer erlaubt, den Zustand darüber zu mutieren.
Records (behandelt in records) bündeln das meiste davon automatisch.
Ein ausgearbeitetes Beispiel
Was kommt als nächstes
private-Felder und final-Unveränderlichkeit sind die Bausteine des nächsten Konzepts: den Zustand vollständig hinter den Methoden einer Klasse zu verbergen. Das Kapitel zur Kapselung führt diese Fäden zusammen.