1. Das Ordnungsproblem in klassischen Kollektionen
In Java gab es schon immer Kollektionen, die die Reihenfolge der Elemente garantieren (zum Beispiel ArrayList, LinkedList, LinkedHashSet, LinkedHashMap), und solche, bei denen die Reihenfolge nicht garantiert ist (zum Beispiel HashSet, HashMap). Doch alle diese Kollektionen hatten einen gemeinsamen Nachteil: Obwohl einige von ihnen die Elemente in einer bestimmten Reihenfolge speichern, boten die Standard-Schnittstellen (List, Set, Map) keine universellen Methoden, um auf das erste und letzte Element zuzugreifen oder die Reihenfolge umzukehren.
Wenn Sie beispielsweise ein List<String> haben, können Sie das erste Element über list.get(0) erhalten, doch bei Set oder Map funktioniert dieser Trick nicht — Sie müssen Iteratoren verwenden oder zusätzlichen Code schreiben. Zugegeben, das ist nicht sehr praktisch und trägt nicht zur Lesbarkeit bei.
Das ist, als hätten Sie einen Schrank mit Schubladen, aber um die erste oder letzte Schublade herauszunehmen, müssten Sie jedes Mal alle Schubladen manuell nachzählen! Es wäre großartig, spezielle Griffe „erste“ und „letzte“ zu haben, oder?
Einführung von SequencedCollection und den zugehörigen Schnittstellen
In Java 21 sind neue Kollektion-Schnittstellen erschienen:
- SequencedCollection<E>
- SequencedSet<E>
- SequencedMap<K, V>
Diese Schnittstellen erweitern die Standardkollektionen und führen einen einheitlichen Ansatz für den Umgang mit der Reihenfolge von Elementen ein. Nun kann man universellen Code für beliebige Kollektionen schreiben, bei denen die Reihenfolge wichtig ist, ohne über die konkrete Implementierung nachdenken zu müssen.
Was ist das?
- SequencedCollection — das ist eine Kollektion, in der die Elemente eine bestimmte Reihenfolge haben, und Sie können leicht das erste, das letzte Element erhalten sowie die Reihenfolge umkehren.
- SequencedSet — dasselbe, aber für Mengen (eindeutige Elemente).
- SequencedMap — dasselbe, aber für Abbildungen (Schlüssel–Wert).
Welche Kollektionen implementieren sie jetzt?
In Java 21 implementieren die neuen Schnittstellen die folgenden Standardkollektionen:
- ArrayList, LinkedList → SequencedCollection
- LinkedHashSet, TreeSet → SequencedSet
- LinkedHashMap, TreeMap → SequencedMap
Das bedeutet, dass Sie diese neuen Möglichkeiten automatisch erhalten, wenn Sie diese Kollektionen bereits verwenden!
2. Zentrale Methoden von SequencedCollection, SequencedSet, SequencedMap
Methoden von SequencedCollection
E getFirst(); // Erstes Element abrufen
E getLast(); // Letztes Element abrufen
SequencedCollection<E> reversed(); // Kollektion in umgekehrter Reihenfolge erhalten
Methoden von SequencedSet
Dieselben Methoden wie bei SequencedCollection, plus alles, was Set bietet.
Methoden von SequencedMap
Map.Entry<K, V> firstEntry(); // Erster Eintrag (Schlüssel–Wert)
Map.Entry<K, V> lastEntry(); // Letzter Eintrag (Schlüssel–Wert)
SequencedMap<K, V> reversed(); // Map in umgekehrter Reihenfolge
3. Beispiele für die Verwendung der neuen Schnittstellen
Beispiel 1: Erstes und letztes Element abrufen
import java.util.*;
public class SequencedDemo {
public static void main(String[] args) {
SequencedCollection<String> sc = new ArrayList<>();
sc.add("Java");
sc.add("Python");
sc.add("Kotlin");
// Erstes und letztes Element abrufen
String first = sc.getFirst(); // "Java"
String last = sc.getLast(); // "Kotlin"
System.out.println("Erstes: " + first);
System.out.println("Letztes: " + last);
}
}
Ausgabe:
Erstes: Java
Letztes: Kotlin
Beispiel 2: Kollektion umkehren
SequencedCollection<String> sc = new LinkedList<>();
sc.add("A");
sc.add("B");
sc.add("C");
SequencedCollection<String> reversed = sc.reversed();
System.out.println(reversed); // [C, B, A]
Beachten Sie: reversed() gibt eine Darstellung der Kollektion in umgekehrter Reihenfolge zurück. Wenn Sie die ursprüngliche Kollektion ändern, ändert sich auch die reversed-Darstellung!
Beispiel 3: Arbeiten mit SequencedSet
SequencedSet<Integer> set = new LinkedHashSet<>();
set.add(100);
set.add(200);
set.add(300);
System.out.println("Erstes Element: " + set.getFirst()); // 100
System.out.println("Letztes Element: " + set.getLast()); // 300
SequencedSet<Integer> reversedSet = set.reversed();
System.out.println(reversedSet); // [300, 200, 100]
Beispiel 4: Arbeiten mit SequencedMap
SequencedMap<String, Integer> map = new LinkedHashMap<>();
map.put("apple", 5);
map.put("banana", 3);
map.put("cherry", 7);
Map.Entry<String, Integer> first = map.firstEntry();
Map.Entry<String, Integer> last = map.lastEntry();
System.out.println("Erstes: " + first.getKey() + " = " + first.getValue()); // apple = 5
System.out.println("Letztes: " + last.getKey() + " = " + last.getValue()); // cherry = 7
SequencedMap<String, Integer> reversedMap = map.reversed();
System.out.println(reversedMap); // {cherry=7, banana=3, apple=5}
4. Was bedeutet das für Ihre Anwendung?
Angenommen, in Ihrer Lernanwendung speichern Sie eine Liste der Benutzer, die sich angemeldet haben, und möchten schnell den ersten und den letzten Benutzer ermitteln (z. B. für die Anzeige „wer sich zuerst angemeldet hat“ und „wer sich zuletzt angemeldet hat“). Früher musste man etwas schreiben wie:
List<String> users = new ArrayList<>();
// ... Benutzer hinzufügen
String first = users.get(0);
String last = users.get(users.size() - 1);
Wenn die Kollektion jedoch kein Liste ist, sondern zum Beispiel ein LinkedHashSet (bei dem die Elemente eindeutig sind und die Reihenfolge erhalten bleibt), funktioniert dieser Trick nicht:
Set<String> users = new LinkedHashSet<>();
// ... Benutzer hinzufügen
// Wie erhält man den ersten? Nur über einen Iterator:
String first = users.iterator().next();
// Und den letzten? Nur indem man alle Elemente durchläuft!
Jetzt ist alles einfacher und universeller:
SequencedSet<String> users = new LinkedHashSet<>();
// ... Benutzer hinzufügen
String first = users.getFirst();
String last = users.getLast();
Das reduziert nicht nur die Code-Menge, sondern macht ihn auch lesbarer und sicherer.
5. Schema der neuen Schnittstellen
classDiagram
Collection <|-- SequencedCollection
List <|-- SequencedCollection
Set <|-- SequencedSet
Map <|-- SequencedMap
SequencedCollection <|-- SequencedSet
SequencedSet <|-- LinkedHashSet
SequencedSet <|-- TreeSet
SequencedCollection <|-- ArrayList
SequencedCollection <|-- LinkedList
SequencedMap <|-- LinkedHashMap
SequencedMap <|-- TreeMap
6. Nützliche Details
Praktische Vorteile von SequencedCollection
- Einheitliche Schnittstelle für die Arbeit mit der Reihenfolge: Sie müssen sich nicht mehr merken, wo es get(0) gibt, wo ein Iterator nötig ist und wo man das erste Element überhaupt nicht erhalten kann.
- Komfort bei der Arbeit mit Queues und Stacks: Einfaches Abrufen des ersten (head) und letzten (tail) Elements.
- Sicherheit und Lesbarkeit: Weniger Fehler im Zusammenhang mit der falschen Verwendung von Kollektionen; der Code wird selbstdokumentierend.
- Schnelles Umkehren der Kollektion: Die Methode reversed() ermöglicht es, die umgekehrte Reihenfolge ohne manuelle Eingriffe zu erhalten.
- Einfachere Wartung und Erweiterung des Codes: Wenn Sie in Zukunft z. B. ArrayList durch LinkedHashSet ersetzen möchten, muss der Code, der SequencedCollection verwendet, nicht umgeschrieben werden.
Implementierungsdetails
- Nicht alle Kollektionen implementieren SequencedCollection: Zum Beispiel HashSet und HashMap garantieren keine Reihenfolge und implementieren daher die neuen Schnittstellen nicht.
- Methoden können Ausnahmen werfen: Wenn die Kollektion leer ist, führt der Aufruf von getFirst() oder getLast() zu einer NoSuchElementException. Vergessen Sie nicht zu prüfen, dass die Kollektion nicht leer ist!
- reversed() — ist eine Darstellung (View), keine Kopie: Änderungen an der ursprünglichen Kollektion spiegeln sich in der umgekehrten Darstellung wider und umgekehrt.
- Kompatibilität: Die neuen Schnittstellen sind erst ab Java 21 verfügbar. Wenn Sie eine ältere Java-Version verwenden, stehen Ihnen diese Möglichkeiten noch nicht zur Verfügung (aber das ist ein hervorragender Grund zu aktualisieren!).
- Generics: Alle neuen Schnittstellen unterstützen Generics vollständig, sodass Sie mit beliebigen Datentypen arbeiten können.
7. Häufige Fehler im Umgang mit SequencedCollection
Fehler Nr. 1: Von Kollektionen Ordnung zu erwarten, die sie nicht garantieren. Wenn Sie versuchen, ein HashSet zu SequencedSet zu casten, erhalten Sie einen Kompilierfehler — HashSet hat keine Reihenfolge und implementiert diese Schnittstelle nicht.
Fehler Nr. 2: Leere Kollektionen ignorieren. Der Aufruf von getFirst() oder getLast() für eine leere Kollektion wirft eine Ausnahme. Stellen Sie vor dem Aufruf dieser Methoden sicher, dass die Kollektion nicht leer ist:
if (!sc.isEmpty()) {
String first = sc.getFirst();
}
Fehler Nr. 3: Missverständnisse mit reversed(). Die Methode reversed() gibt eine Darstellung zurück, keine Kopie. Wenn Sie die reversed-Darstellung ändern, ändert sich auch die ursprüngliche Kollektion (und umgekehrt). Das kann zu unerwarteten Ergebnissen führen, wenn Sie ein solches Verhalten nicht erwarten.
Fehler Nr. 4: Verwendung der neuen Interfaces auf älteren Java-Versionen. Wenn Ihr Projekt mit einer Version unter Java 21 kompiliert wird, findet der Compiler diese Schnittstellen nicht. Prüfen Sie die Version des JDK in den Projekteinstellungen!
GO TO FULL VERSION