Veränderbarkeit
Ziele
- Ich kann eine Möglichkeit aufzeigen, wie Felder nie überschrieben werden dürfen.
- Ich kann erklären, wie die Objekte von eigenen Klassen “unveränderlich” gemacht werden können.
- Ich kann auswendig erklären, welche Vorteile unveränderliche Klassen bieten.
Felder unveränderlich machen
Manchmal wirst du in Klassen Felder haben, die sich nie ändern werden - und auch nie sollten. Damit sich ein Feld nicht ändern kann, kannst du das Keyword final
vor dem Datentyp angeben. Im folgenden Beispiel haben wir eine Klasse für einen Schweizer Staatsbürger, der einen Namen besitzen kann, der sich ändern kann. Zusätzlich müssen alle BürgerInnen eine AHV-Nummer (socialSecurityNumber
), die sich (im Normalfall) nie ändert:
|
|
Das final
-Keyword bewirkt bei Instanzvariablen, dass sie nur einmal gesetzt werden können - entweder direkt oder spätestens im Konstruktor.
Nachher darf der Wert dieser Variable nicht mehr geändert werden, wieso folgender Code zu einem Kompilierfehler führt:
|
|
Somit bietet das final
-Keyword eine gute Möglichkeit an zu garantieren, dass ein Feld nicht später aus Versehen verändert wird.
Veränderungen an Objekten - Immutable & mutable objects
In der Programmierung gibt es ein wichtiges Konzept, das “Unveränderlichkeit” (Englisch: Immutability) genannt wird. Unveränderlichkeit bedeutet, dass ein Objekt nie seine Werte ändert. Wenn wir diese Werte ändern wollen, müssen wir ein neues Objekt erstellen.
Das klassische Beispiel ist die Klasse String
. Zeichenfolgen sind unveränderliche Objekte, sodass alle String-Operationen einen neuen String erzeugen.
|
|
Unsere neudefinierte Klasse Color
hingegen ist nicht unveränderlich:
|
|
Da Color
in diesem Beispiel veränderlich ist, kann jedes Feld verändert werden:
|
|
Die letzte Zeile führt zu dieser Ausgabe, die falsch ist:
|
|
Das Problem mit unserem Code ist, dass wir erwarten, dass Color.RED
dem Wert von new Color(255, 0, 0)
entspricht, da die statische Variable sogar mit final
deklariert wurde. Und das würde auch jede(r) andere Entwickler(in) erwarten!
Das Problem war, dass wir immer wieder auf das gleiche Objekt zugegriffen haben, das wir auch bearbeitet haben, was dieses Diagramm verdeutlicht:
Möchten wir, dass Color
diesen Erwartungen gerecht wird, dann müssen wir sie unveränderlich (immutable
) machen. Dann ist jedes ihrer Felder unveränderlich. Dies geschieht mit dem final
-Key-Word vor allen Feldern innerhalb der Klasse:
|
|
Nun führt die Anweisung color.green = 255;
zu einem Fehler, weil das Feld green
unveränderlich ist und deshalb nicht geändert werden kann.
Der Vorteil von diesem Code ist nun, dass Color.RED
immer einen Wert zurückgibt, der wirklich die Farbe Rot repräsentiert.
Ein zweites Beispiel
Oft lohnt es sich, Klassen die dafür verwendet werden, um Daten in einem Objekt zwischen zu speichern, immutable zu definieren. Nehmen wir hierzu wieder die Klasse Color
, die genau diesem Zweck dient.
Im ersten Beispiel gehen wir davon aus, dass Color
veränderlich ist (also keine final
-Keywords vor den Instanzvariablen). Hier bieten wir eine statische Methode an, die aus einer Farbe einen Grau-Wert produziert:
|
|
Logischer Weise führt dieser Code zu einer unerwünschten Ausgabe in der Konsole. Offensichtlich ist der Fehler in der convertToGrayScale(...)
-Methode, wo wir die Instanz-Variablen vom Parameter überschreiben, was wir nicht sollten. Obwohl dieses Beispiel den Fehler sehr offensichtlich begeht, ist das ein Fehler, der sehr oft passiert:
Methoden, die einen neuen Wert berechnen oder holen (
get
), sollten im Normalfall bestehende Werte nicht bearbeiten. Sehr oft führt das dazu, dass Dinge passieren, die man als Aufrufer nicht erwartet und man muss Fehler an unerwarteten Stellen suchen, weil die Methode mehr macht als zu erwarten ist.
Solche
convert...
- bzw.get...
-Methoden sollten einen neuen Wert zurückgeben, anstatt einen bestehenden zu verändern. Ein Indiz dafür, dass dieses Prinzip verletzt wird, ist oft, dass eineget...()
-Methode den Rückgabe-Wertvoid
besitzt (hier nicht der Fall).
Oft ist es nicht so offensichtlich wie hier. Schreibt man die Klassen von Anfang an immutable, so ist der Code ganz generell viel weniger Fehler anfällig (erst recht, wenn der Code parallel auf mehreren Prozessoren läuft) und das Vorgehen führt dazu, dass zukünftiger Code automatisch lesbarer (aka “schöner”) wird - was dir in Zukunft sicherlich viel Zeit ersparen wird.
Die gleiche convertToGrayScale(color)
müsste mit der immutable-Version der Klasse umgeschrieben werden, damit kein Kompilierfehler auftritt (und nun funktioniert sie wie erwartet):
|
|
Der Hauptunterschied mit immutable
Klassen ist daher, dass so viel öfters neue Instanzen generiert werden.
Jetzt bist du dran. Löse alle verbleibenden OOP Aufgaben.