Freitag, 23. August 2019

Veränderliche und unveränderliche Objekte

Die meisten Objekte in Java sind veränderlich (eng. mutable). Damit ist gemeint, dass der Status der Objekte, also die Werte ihrer Attribute, verändert werden kann. Veränderliche Objekte haben den Vorteil, dass wir beliebig mit ihnen verfahren und arbeiten können, weil sie veändert werden können. Sie haben allerdings den Nachteil, dass wir beliebig mit ihnen verfahren und arbeiten können, weil sie veändert werden können. Sie sehen hier, dass ein Vorteil auch zu einem Nachteil werden kann. Warum ist das so?

Manchmal möchte man keine Objekte haben, deren Status sich verändern kann. Die Werte der Attribute sollen immer konstant bleiben. Ein erster naiver Ansatz, um dies zu erreichen, könnte der folgende sein:

final Person meinePerson = new Person("Ali", "Baba");

Mit final können Konstanten definiert werden. Also liegt irgendwo der Gedanke nahe, dass nun auch das Objekt konstant ist und damit auch seine Attribute. Dieser Eindruck täuscht. Was hier konstant ist, ist die Referenz. Der folgende Code wird also vom Compiler nicht akzeptiert werden:

final Person meinePerson = new Person("Ali", "Baba");
final Person meineAnderePerson = new Person("Elle", "Fant");
meinePerson = meineAnderePerson;


Wir verändern in der dritten Zeile die Referenz und genau das wird durch das Schlüsselwort final verhindert. Was aber weiterhin funktionieren wird und was wir eigentlich verhindern wollten, zeigt der folgende Code-Ausschnitt:

final Person meinePerson = new Person("Ali", "Baba");
meinePerson.setVorname("Elle");
meinePerson.setNachname("Fant");


Also dieses schöne Schlüsselwort final führt uns nicht in die richtige Richtung. Schade. Um ein Objekt wirklich unveränderlich hinzubekommen, müssen wir uns etwas mehr Arbeit gönnen. Ein Objekt ist unveränderlich, wenn die Werte seiner Attribute nicht verändert werden können. Die Klasse muss also die folgenden Regeln erfüllen:

  1. Die Klasse darf keine Methoden anbieten, die schreibende Zugriffe auf die Attribute ermöglichen wie z.B. Setter.
  2. Alle Attribute sind privat und final.
  3. Wenn die Klasse Referenzen auf andere Objekte enthält, müssen diese Objekte natürlich auch unveränderlich sein.
  4. Wenn die Klasse eine Collection enthält, darf es keine Methoden geben, die das Verändern der Elemente dieser Collection ermöglichen, also keine Methoden zum Löschen, Hinzufügen usw.
  5. Die Klasse selbst ist als final deklariert. Damit können keine Unterklassen gebildet werden, die eventuell unerwünschtes Verhalten in die Objekte einschleusen.

Beispiele für unveränderliche Objekte im JDK sind die Wrapperklassen wie Integer. Sie bieten keinerlei Möglichkeit an, ihre Werte noch einmal zu verändern, nachdem die Objekte einmal erzeugt wurden.

Welche Voteile haben diese unveränderlichen Objekte jetzt?

  1. Ihre Werte können nicht aus Versehen verändert werden. Da in Java Objekte nur als Referenzen durch das Programm geschoben werden, ist manchmal nicht leicht zu sehen, dass man tatsächlich ein Objekt verändert. Dazu gleich noch mehr.
  2. Sie sind leichter zu entwickeln, da Klassen-Invarianten nur einmal überprüft werden müssen. Wenn man im Konstruktor prüft, ob die übergebenen Attributwerte korrekt sind, muss man keine weitere Überprüfungen mehr einbauen.
  3. Der interne Status bleibt konsistent, auch nachdem eine Exception geworfen wurde. Dies ist nicht bei allen veränderlichen Objekten der Fall.
  4. Unveränderliche Objekte sind automatisch thread-sicher, so dass man sich um Synchronisierung keine Gedanken machen muss.

Was meine ich nun damit, dass Programmierfehler vermieden werden können? Sehen Sie sich mal das nächste Code-Beispiel an.

public Person getMyPerson() {
    // Hier wird eine Person erzeugt und irgendwas tolles wird mit der Person getan.
    return person;
}

public void tueEtwasMitPerson(Person person) {
    person.setVorname("Elle");
    person.setNachame("Fant");

}

In Java wird oft übersehen, dass Objekte immer Referenzen sind. Die Methode getMyPerson() gibt also eine Referenz auf das Originalobjekt zurück. Die Methode tueEtwasMitPerson() erhält eine Referenz auf ein Objekt und wir verändern das Originalobjekt. Das kann erwünscht sein; das kann aber auch unerwünscht sein. Und genau an dieser Stelle greifen die unveränderlichen Objekte. Wenn Sie sicherstellen möchten, dass es keine unerwünschten Veränderungen geben kann, verwenden Sie unveränderliche Objekte.

Nun haben die unveränderlichen Objekte in Java einen immensen Nachteil: Klassen müssen von vornherein so konzipiert werden, dass ihre Objekte unveränderlich sind. Es ist nicht möglich, ein Objekt a der Klasse A veränderlich zu definieren und ein Objekt b der Klasse A veränderlich. Sie müssen die Klasse mit den oben beschriebenen Maßnahmen unveränderlich konzipieren. Daher kann es in manchen Fällen sinnvoll sein, zwei Klassen in seinem Programm zu haben wie z.B. Person und PersonImmutable.

Dieser Nachteil löst sich aber auch wieder in einen Vorteil auf: Sie können in Java natürlich auch nur teil-veränderliche Klassen entwickeln, was man sowieso tut, wenn man Attribute anständig kapselt.

Mit diesem Beitrag wollte ich nicht sagen, dass Objekte unveränderlich sein sollten. In vielen Fällen möchte man Attribute ja verändern können. Dennoch sollten Sie die Tatsache, was veränderlich bedeutet, unbedingt im Kopf behalten. Da sind schon besseren Programmieren mit Java schwere Fehler unterlaufen. Die Problematik ist wichtig und manchmal kann es wirklich ganz sinnvoll sein, vollkommen unveränderliche Objekte zu definieren.

Keine Kommentare:

Kommentar veröffentlichen