7.6 Finale Klassen und finale Methoden
Bisher haben wir den Modifizierer final immer nur im Zusammenhang mit Variablen kennengelernt. Dann bedeutet er, dass die Variable nur einmal zur Initialisierung beschrieben werden darf. final taucht aber auch als Modifizierer bei Klassen und Methoden auf.
7.6.1 Finale Klassen
Soll eine Klasse keine Unterklassen bilden, werden Klassen mit dem Modifizierer final versehen. Dadurch lässt sich vermeiden, dass Unterklassen Eigenschaften nachträglich verändern. Ein Versuch, von einer finalen Klasse zu erben, führt zu einem Compilerfehler. Dies schränkt zwar die objektorientierte Wiederverwendung ein, wird aber aufgrund von Sicherheitsaspekten in Kauf genommen. Eine Passwortüberprüfung soll zum Beispiel nicht einfach überschrieben werden können.
In der Java-Bibliothek gibt es eine Reihe finaler Klassen, von denen wir einige bereits kennen:
-
String, StringBuilder
-
Integer, Double … (Wrapper-Klassen)
-
Math
-
System, Locale
-
Color
[+] Tipp
Eine protected-Eigenschaft in einer als final deklarierten Klasse ergibt wenig Sinn, da ja keine Unterklasse möglich ist, die diese Methode oder Variable nutzen kann. Daher sollte die Eigenschaft dann paketsichtbar sein (protected enthält ja »paketsichtbar«) oder gleich private oder public.
7.6.2 Nicht überschreibbare (finale) Methoden
Nehmen wir an, ein Ereignis hat eine format()-Methode, die eine String-Repräsentation nach der ISO-8601-Norm zurückgibt und das auch dokumentiert:
public class Event {
public int duration;
/**
* @return a string representation of this event duration using ISO-8601.
*/
public String format() {
return Duration.ofMinutes( duration ).toString();
}
}
So sieht der String zum Beispiel aus:
Event flight = new Event();
flight.duration = 6 /* h */ * 60 + 12 /* min */;
System.out.println( flight.format() ); // PT6H12M
Eine Unterklasse SportsEvent findet nun diese Notation zu schwer zu lesen und stellt sie anders dar:
public class SportsEvent extends Event {
@Override public String format() {
return duration + " minutes";
}
}
Das Ergebnis ist wie zu erwarten:
Event schalkeBvb = new SportsEvent();
schalkeBvb.duration = 90;
System.out.println( schalkeBvb.format() ); // 90 minutes
Das Problem hierbei ist ein Verstoß gegen das liskovsche Substitutionsprinzip. Eine Unterklasse darf den »Vertrag« nicht brechen, und wenn der Basistyp ein gewisses Format verspricht, dann müssen sich Unterklassen auch daran halten.
Ein Lösungsansatz besteht darin, den Unterklassen das Überschreiben einer Methode zu verbieten. Das wird durch den Modifizierer final an der Methodendeklaration erreicht:
final public String format() {
return Duration.ofMinutes( duration ).toString();
}
Nehmen wir in Event bei der Methode format() den Modifizierer final hinzu, dann meldet der Compiler bei der Unterklasse SportEvent einen Fehler:
'format()' cannot override 'format()' in [...].Event'; overridden method is final
Da Methodenaufrufe immer dynamisch gebunden werden, kann ein Aufrufer nicht mehr unbeabsichtigt in der Unterklasse landen – finale Methoden verhindern das.
[»] Hinweis
Auch private Methoden können final sein, aber private Methoden lassen sich ohnehin nicht überschreiben (sie werden überdeckt), sodass final überflüssig ist.