Datenkapselung

Aus Programmieren-Wiki
(Weitergeleitet von Getter / Setter für Listen)
🚧 Diese Seite befindet sich in Bearbeitung 🚧
🤓 Diese Seite ist eine Bewertungsrichtlinie, die ab Blatt 2 annotiert und ab Blatt 3 abgezogen wird. 🤓

Beschreibung

Es sollten nie komplexe veränderliche Objekte, Arrays oder Collections (z.B. ArrayList, HashMap) in direkter Form gesetzt oder ausgegeben werden, um einen Geheimnisverrat und eine Manipulation der Objekte zu verhindern. Betrachten wir hierzu ein kleines Beispiel:

// Innerhalb unserer Klasse
private int[] array;
 
public void setSomeArray(int[] array) {
    this.array = array;
}
 
// Und an einer anderen Stelle
int[] somewhere = {0, 42, 1337, 4711};
setSomeArray(somewhere);
somewhere[2] = 666; // Manipulation durch böse Geister!

Nach dem Setzen des Arrays somewhere wird der Klasse in einer Ausgabe Methode etwaiges zu sehen sein: {0, 42, 1337. 4711}. In der letzten Zeile also insbesondere nach Festlegung des privaten und internen Arrays, verändern wir vermeintlich nur das außerhalb liegende Array somewhere und müssen leider feststellen durch unsere nach wie vor gültige Objektrefenzierung erhalten wir nun auch in der Klasse Ausgabe des Arrays array: {0, 666, 1337, 4711}. Ähnliche Beispiele können wir konstruieren nach Ausgabe eines internen Arrays.

Erstes Ziel sollte demnach sein, die Schnittstellen der Klasse so zu gestalten, dass überhaupt keine Arrays oder Collections nach außen gegeben werden müssen, bspw. durch anbieten von Methoden zum Hinzufügen oder Entfernen einzelner Elemente anhand bestimmter Eigenschaften statt der Gesamtmenge.

Eine einfache Variante, dieses Problem dennoch zu umgehen, ist, eine echte Kopie der jeweiligen übergebenen oder auszugebenden Objekte zu erstellen. Für auszugebende Objekte reicht oftmals auch eine unveränderliche Ansicht des Objekts, das heißt ein Wrapper-Objekt welches Daten vom ursprünglichen Objekt liest, aber keine verändernden Operationen zulässt.

Methoden der Standardbibliothek

Collections
Kopien von Collections können mit List.copyOf bzw. den äquivalenten Methoden in Set und Map erzeugt werden. Die Kopie ist hierbei immer unveränderbar; ist explizit eine veränderbare Collection gefragt kann direkt der Copy-Konstruktur der gewünschten Datenstruktur verwendet werden (z.B. ArrayList(Collection)).
Unveränderbare Ansichten erstellt Collections.unmodifiableCollection bzw. die spezifischen Methoden für List, Set und weitere Interfaces.
Anmerkung zu Records: Da diese immer unveränderbar sein sollten, empfiehlt es sich die List/Set/Map.copyOf Methoden im Konstruktor zu verwenden. Damit ist gewährleistet, dass spätere Änderungen an der übergebenen Collection keine Auswirkung auf das Objekt haben, und Änderungen an der im Record gespeicherten Collection nicht möglich sind.
Arrays
Arrays.copyOf kopiert ein Array. Unveränderbare Ansichten sind nur mit Einsatz von Collections möglich, da Arrays selber immer veränderbar sind: Collections.unmodifiableList(Arrays.asList(array)). Außerdem kann mit List.of eine Kopie des Arrays als unveränderbare Liste erstellt werden.
Wichtig: alle genannten Methoden Kopieren nur die Datenstruktur selber, die enthaltenen Objekte werden nicht kopiert. Sind auch die Objekte veränderbar, muss manuell eine sogenannte Deep-Copy erstellt werden, bei der rekursiv alle veränderbaren Objekte kopiert werden. Oft ist es aber sinnvoller, veränderbare Strukturen zu vermeiden und eigene komplexe Datentypen nicht veränderlich zu gestalten, bspw. Koordinaten oder anderweitige Tupel.

Negativbeispiel:

public void setSomeArray(int[] array) {
    this.array = array;
}
 
public int[] getSomeArray() {
    return this.array;
}

Positivbeispiel:

public void setSomeArray(int[] array) {
    this.array = new int[array.length];
    System.arraycopy(array, 0, this.array, 0, array.length);
}
 
public int[] getSomeArray() {
    int[] copy = new int[this.array.length];
    System.arraycopy(this.array, 0, copy, 0, copy.length);
    return copy;
}