CodeGym /Kurse /JAVA 25 SELF /Unveränderliche Kollektionen: Collections.unmodifiable

Unveränderliche Kollektionen: Collections.unmodifiable

JAVA 25 SELF
Level 33 , Lektion 2
Verfügbar

1. Das Problem der Änderbarkeit von Kollektionen

In Java ähneln Kollektionen einem Lagerhaus mit Waren: Jeder kann kommen und etwas hinzufügen, entfernen, ändern. Manchmal ist das praktisch, aber in großen Programmen wird es zur Kopfschmerzquelle. Stellen Sie sich vor, Sie geben die Produktliste Ihrer Klasse nach außen weiter, und jemand löscht die Hälfte der Einträge. Oder – noch „lustiger“ – in einem Multithreading-Programm fügt ein Thread Elemente hinzu, während ein anderer sie liest: Das Ergebnis kann unerwartet sein und Fehler schwer greifbar (z. B. ConcurrentModificationException).

Hier ist ein Beispiel, warum die Änderbarkeit von Kollektionen eine Fehlerquelle ist:

import java.util.*;

public class Inventory {
    private List<String> products = new ArrayList<>();

    public Inventory() {
        products.add("Tee");
        products.add("Kaffee");
    }

    public List<String> getProducts() {
        // GEFÄHRLICH! Wir geben eine Referenz auf die interne Liste zurück
        return products;
    }
}
public class Main {
    public static void main(String[] args) {
        Inventory inv = new Inventory();
        List<String> external = inv.getProducts();
        external.remove("Tee"); // Hoppla! Jetzt gibt es im Inventar keinen Tee mehr
        System.out.println(inv.getProducts()); // [Kaffee]
    }
}

Haben Sie den Haken bemerkt? Eine Methode gibt die interne Kollektion zurück, eine andere ändert sie. So kann man unbeabsichtigt Daten zerstören, die geschützt sein sollten.

2. Unveränderliche Kollektionen erstellen: Collections.unmodifiable*

Um solche Pannen zu vermeiden, bietet Java wirksamen Schutz: Man kann eine Kollektion mit speziellen Wrappern aus der Klasse Collections „unveränderlich“ machen:

  • Collections.unmodifiableList(list)
  • Collections.unmodifiableSet(set)
  • Collections.unmodifiableMap(map)

Wie funktioniert das? Zuerst erstellen Sie eine normale Kollektion, und dann packen Sie sie in einen „unveränderlichen“ Wrapper:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> drinks = new ArrayList<>();
        drinks.add("Tee");
        drinks.add("Kaffee");

        List<String> immutableDrinks = Collections.unmodifiableList(drinks);

        System.out.println(immutableDrinks); // [Tee, Kaffee]

        // Versuchen wir, ein Element hinzuzufügen
        immutableDrinks.add("Kakao"); // Zack! UnsupportedOperationException
    }
}

Der Versuch, eine solche Kollektion zu ändern, führt zu einer UnsupportedOperationException. Das ist, als hätten Sie einen riesigen Aufkleber „NICHT ANFASSEN!“ auf die Kiste geklebt – und jeder, der versucht, etwas hinzuzufügen oder zu löschen, bekommt auf die Finger (oder auf den Call-Stack).

Beispiel: den internen Zustand schützen

Korrigieren wir unsere Klasse Inventory aus dem vorherigen Beispiel:

import java.util.*;

public class Inventory {
    private List<String> products = new ArrayList<>();

    public Inventory() {
        products.add("Tee");
        products.add("Kaffee");
    }

    public List<String> getProducts() {
        // Jetzt geben wir einen Wrapper zurück
        return Collections.unmodifiableList(products);
    }
}

Wenn jetzt jemand versucht, die erhaltene Liste zu ändern, bekommt er eine Ausnahme.

3. Verhalten unveränderlicher Kollektionen: oberflächlicher Schutz

Wichtig: unmodifiableList und seine „Geschwister“ sind nur ein Wrapper um die Ausgangskollektion. Sie erstellen keine Kopie – alle Änderungen an der Ausgangskollektion (derjenigen „innen“) sind auch im Wrapper sichtbar!

Demonstration

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> drinks = new ArrayList<>();
        drinks.add("Tee");
        List<String> immutableDrinks = Collections.unmodifiableList(drinks);

        drinks.add("Kaffee"); // Wir ändern die Ausgangskollektion
        System.out.println(immutableDrinks); // [Tee, Kaffee] – das Element ist erschienen!
    }
}

Fazit: Der Wrapper schützt nur vor Änderungen über den Wrapper selbst. Wenn jemand eine Referenz auf die Ausgangskollektion hält, kann er sie weiterhin ändern.

4. Tiefe Unveränderlichkeit: Mythen und Realität

Die Wrapper unmodifiable* machen die Kollektion nur von außen unveränderlich. Enthält die Kollektion veränderliche Objekte, kann man diese weiterhin ändern!

Beispiel

import java.util.*;

class Product {
    String name;
    Product(String name) {
        this.name = name;
    }
    public String toString() {
        return name;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Product> products = new ArrayList<>();
        products.add(new Product("Tee"));
        List<Product> immutableProducts = Collections.unmodifiableList(products);

        // Wir ändern das Objekt innerhalb der Kollektion
        immutableProducts.get(0).name = "Kaffee";
        System.out.println(immutableProducts); // [Kaffee]
    }
}

Fazit:

  • Die Kollektion ist „unveränderlich“, aber die Objekte darin sind es nicht.
  • Für vollständige (tiefe) Unveränderlichkeit verwenden Sie unveränderliche Objekte (z. B. String, Integer, record-Klassen) oder gestalten Sie Ihre eigenen Klassen unveränderlich (immutable).

5. Wann sollte man unveränderliche Kollektionen verwenden?

Zum Schutz des internen Zustands

Wenn Sie eine Klasse schreiben, die eine Kollektion speichert, und diese nach außen zurückgeben, geben Sie immer einen Wrapper zurück, damit niemand Ihre Daten versehentlich (oder absichtlich) ändern kann:

public List<String> getProducts() {
    return Collections.unmodifiableList(products);
}

In Multithreading-Programmen

In Multithreading-Anwendungen sind veränderliche Kollektionen eine Problemquelle (Race Condition, ConcurrentModificationException und andere „Freuden des Lebens“). Wenn eine Kollektion nach der Erstellung nicht geändert werden muss – machen Sie sie unveränderlich.

Für die Datenübergabe zwischen Schichten

Wenn Sie eine Kollektion von einer Schicht der Anwendung in eine andere übergeben (z. B. vom DAO in den Service), übergeben Sie eine unveränderliche Kopie oder einen Wrapper – das schützt vor unbeabsichtigten Änderungen.

6. Praktische Beispiele

Beispiel 1: die Studierendenliste schützen

import java.util.*;

public class Group {
    private final List<String> students = new ArrayList<>();

    public void addStudent(String name) {
        students.add(name);
    }

    public List<String> getStudents() {
        return Collections.unmodifiableList(students);
    }
}

Jetzt kann niemand über getStudents() direkt Studierende hinzufügen oder entfernen.

Beispiel 2: unveränderliche Map

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> grades = new HashMap<>();
        grades.put("John", 5);
        grades.put("Mary", 4);

        Map<String, Integer> immutableGrades = Collections.unmodifiableMap(grades);

        // immutableGrades.put("Peter", 3); // UnsupportedOperationException
    }
}

7. Nützliche Nuancen

Moderne Alternativen: List.of, Set.of, Map.of

In Java 9 sind noch bequemere Möglichkeiten entstanden, unveränderliche Kollektionen zu erstellen:

List<String> drinks = List.of("Tee", "Kaffee");
Set<String> fruits = Set.of("Apfel", "Banane");
Map<String, Integer> ages = Map.of("John", 20, "Mary", 21);
  • Diese Kollektionen sind unveränderlich (jeder Änderungsversuch führt zu einer Ausnahme).
  • Sie haben keine „Ausgangs“-Kollektion (im Gegensatz zu Collections.unmodifiable*).
  • Sie erlauben keinen Wert null.

Außerdem gibt es seit Java 10 Kopier-Methoden: List.copyOf, Set.copyOf, Map.copyOf – sie erstellen eine unveränderliche Kopie der übergebenen Kollektion.

Vergleich der Methoden zur Erstellung unveränderlicher Kollektionen

Methode Tiefe Unveränderlichkeit Kann man die Ausgangskollektion ändern? Erlaubt null? Java-Version
Collections.unmodifiableList(list)
Nein Ja Ja 1.2
List.of(...), Set.of(...), Map.of(...)
Nein Nein (keine zugrunde liegende Kollektion) Nein 9+

8. Typische Fehler beim Arbeiten mit unveränderlichen Kollektionen

Fehler Nr. 1: Die Ausgangskollektion nach dem Erstellen des Wrappers ändern. Sie haben unmodifiableList erstellt, und danach ändert jemand die Ausgangsliste. Der Wrapper schützt davor nicht – die Änderungen sind überall dort sichtbar, wo der Wrapper verwendet wird.

Fehler Nr. 2: Erwartung tiefer Unveränderlichkeit. Viele denken, wenn die Kollektion unveränderlich ist, könne man die Objekte darin ebenfalls nicht ändern. Tatsächlich wird nur die Struktur geschützt (Hinzufügen/Entfernen/Ändern über die Kollektion), nicht jedoch der Inhalt der Objekte.

Fehler Nr. 3: Verwendung unveränderlicher Kollektionen mit dem Wert null in modernen Fabriken. Mit List.of, Set.of, Map.of erstellte Kollektionen erlauben kein null. Der Versuch, null hinzuzufügen oder abzurufen, führt zu einer Ausnahme.

Fehler Nr. 4: Weitergabe von Referenzen auf veränderliche Kollektionen nach außen. Wenn Sie nach außen eine Referenz auf die interne Kollektion zurückgeben (ohne Wrapper), verlieren Sie die Kontrolle über Ihre Daten – ein direkter Weg zu Bugs und dem „Auslaufen“ von Invarianten.

Fehler Nr. 5: Verwendung unveränderlicher Kollektionen in Code, der Änderbarkeit erwartet. Wenn Fremdcode versucht, die Kollektion zu ändern (z. B. ein Element hinzuzufügen), erhält er eine UnsupportedOperationException. Stellen Sie sicher, dass die Verbraucher über die Unveränderlichkeit der Daten Bescheid wissen.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION