
Sequenced Collections sind Collections, deren Elemente eine festgelegte Reihenfolge haben – Listen, Deques, LinkedHashSet, TreeSet und die entsprechenden Maps. Seit Java 21 haben sie gemeinsame Methoden, mit denen du auf das erste und das letzte Element zugreifen und die Elemente in umgekehrter Reihenfolge durchlaufen kannst.
Das letzte Element einer Liste kannst du seit Java 21 mit getLast() lesen. Bis Java 20 musstest du dafür den Index des letzten Elements ausrechnen:
List<String> pioneers = List.of("Ada", "Grace", "Alan");
// bis Java 20
System.out.println(pioneers.get(pioneers.size() - 1));
// seit Java 21
System.out.println(pioneers.getLast());
Alan
Alan
Beim LinkedHashSet ist der Unterschied größer. Für das erste Element musstest du bis Java 20 einen Iterator einsetzen, für das letzte Element eine Schleife über das gesamte Set:
LinkedHashSet<String> tags =
new LinkedHashSet<>(List.of("java", "collections", "api"));
// bis Java 20
String first = tags.iterator().next();
String last = null;
for (String tag : tags) {
last = tag;
}
System.out.println(first + ", " + last);
// seit Java 21
System.out.println(tags.getFirst() + ", " + tags.getLast());
java, api
java, api
Die neuen Methoden stammen aus drei Interfaces, die durch JEP 431 in Java 21 eingeführt wurden: SequencedCollection, SequencedSet und SequencedMap.
In diesem Artikel erfährst du,
- welche Collections eine festgelegte Reihenfolge haben und welche nicht,
- wo die drei neuen Interfaces in der Hierarchie des Collections Frameworks stehen,
- wie sich
getFirst(),addFirst()undremoveFirst()bei leeren und unveränderlichen Collections verhalten, - warum
reversed()eine View liefert und keine Kopie, - was
addFirst()bei einemLinkedHashSettut und warum einTreeSetdie Methode ablehnt, - welche Methoden
SequencedMapmitbringt und warum essequencedKeySet()nebenkeySet()gibt, - welche Fallstricke du kennen solltest.
Was ist eine Sequenced Collection?
Eine Sequenced Collection hat eine festgelegte Reihenfolge ihrer Elemente, die sogenannte Encounter Order – die Reihenfolge, in der ein Iterator die Elemente liefert. In dieser Reihenfolge gibt es ein erstes und ein letztes Element.
Die Reihenfolge entsteht auf eine von zwei Arten. Bei einer Liste, einem Deque, einem LinkedHashSet und einer LinkedHashMap bestimmt dein Code die Position eines Elements – durch den Index oder dadurch, wann und an welchem Ende das Element eingefügt wird. Bei einem TreeSet und einer TreeMap bestimmt dagegen die Sortierung die Position, also die natürliche Ordnung der Elemente oder ein Comparator.
Die folgende Tabelle zeigt, welche Klassen des JDK eine Encounter Order haben, woher sie stammt und über welches Interface die Klasse seit Java 21 eine Sequenced Collection ist:
| Klasse | Reihenfolge der Elemente | Sequenced über |
|---|---|---|
ArrayList, LinkedList | Index | List |
ArrayDeque | Einfügen am Anfang oder am Ende | Deque |
LinkedHashSet | Einfügereihenfolge | SequencedSet |
TreeSet | Sortierung | SortedSet |
LinkedHashMap | Einfügereihenfolge der Schlüssel | SequencedMap |
TreeMap | Sortierung der Schlüssel | SortedMap |
HashSet, HashMap | keine festgelegte | ✗ |
PriorityQueue | festgelegt ist nur das Element am Kopf | ✗ |
Beim HashSet und bei der HashMap ist die Reihenfolge der Elemente nicht festgelegt, sie kann sich beim Einfügen weiterer Elemente ändern. Eine PriorityQueue legt nur fest, welches Element als Nächstes entnommen wird – ein letztes Element gibt es dort nicht. Mehr dazu erfährst du im Artikel Queue vs. Deque.
Die drei Interfaces in der Hierarchie
Die folgenden beiden Klassendiagramme zeigen die neuen Interfaces in der Hierarchie der Collections und in der der Maps. Eingezeichnet sind außerdem die konkreten Klassen aus java.util und java.util.concurrent, die eines der drei neuen Interfaces implementieren und deren Name im Quellcode des JDK mindestens 50-mal vorkommt. Durchgezogene Linien stehen für extends, gestrichelte für implements. Oberklassen wie HashSet oder AbstractList lassen die Diagramme weg.
Die drei Interfaces, die mit Java 21 eingeführt wurden, sind dunkelblau umrandet. Hellblau umrandet sind die älteren Interfaces und Klassen, die eine Encounter Order haben und die seit Java 21 direkt oder indirekt eines der neuen Interfaces erweitern oder implementieren, grau sind die Interfaces ohne Encounter Order. Auch Queue ist grau: Deque erweitert zwar Queue, aber Queue selbst hat keine Encounter Order.
Bei den Maps steht SequencedMap zwischen Map und SortedMap:
Die Methoden getFirst(), addFirst() und ihre Gegenstücke für das Ende, getLast() und addLast(), hatte Deque schon seit Java 6. JEP 431 hat sie von dort in das neue Interface gezogen, daher ihre Namen. In Collection selbst stehen sie nicht, denn Collection umfasst auch Collections ohne Encounter Order – ein getFirst() auf einem HashSet beispielsweise hätte keine eindeutige Antwort.
Was dir die gemeinsamen Interfaces bringen, zeigt die folgende Methode. Sie gibt das erste und das letzte Element einer beliebigen Sequenced Collection aus:
static void printEnds(SequencedCollection<?> collection) {
System.out.println(collection.getFirst() + " bis " + collection.getLast());
}
Dieselbe Methode funktioniert für eine Liste, ein Deque, ein LinkedHashSet und ein TreeSet:
printEnds(List.of("Ada", "Grace", "Alan"));
printEnds(new ArrayDeque<>(List.of("Ada", "Grace", "Alan")));
printEnds(new LinkedHashSet<>(List.of("java", "collections", "api")));
printEnds(new TreeSet<>(List.of(21, 17, 25)));
Ada bis Alan
Ada bis Alan
java bis api
17 bis 25
Bis Java 20 hatten diese vier Klassen keinen gemeinsamen Typ mit einer solchen Methode. Du hättest printEnds() für jede von ihnen separat schreiben müssen – mit get(0) für die Liste, getFirst() für das Deque, einem Iterator für das LinkedHashSet und first() für das TreeSet.
Erstes und letztes Element lesen, einfügen und entfernen
SequencedCollection definiert sechs Methoden für die beiden Enden einer Collection:
getFirst()undgetLast()liefern das erste bzw. das letzte Element.addFirst(E)undaddLast(E)fügen ein Element am Anfang bzw. am Ende ein.removeFirst()undremoveLast()entfernen das erste bzw. das letzte Element und geben es zurück.
Das folgende Beispiel liest beide Enden einer ArrayList, fügt an beiden Enden ein Element ein und entfernt die beiden wieder:
List<String> pioneers = new ArrayList<>(List.of("Ada", "Grace", "Alan"));
System.out.println(pioneers.getFirst());
System.out.println(pioneers.getLast());
pioneers.addFirst("Charles");
pioneers.addLast("Linus");
System.out.println(pioneers);
System.out.println(pioneers.removeFirst());
System.out.println(pioneers.removeLast());
System.out.println(pioneers);
Ada
Alan
[Charles, Ada, Grace, Alan, Linus]
Charles
Linus
[Ada, Grace, Alan]
addLast() tut bei einer Liste dasselbe wie add(). addFirst() entspricht add(0, …).
Das hat bei einer ArrayList seinen Preis: addFirst() und removeFirst() verschieben alle übrigen Elemente im internen Array um eine Position – bei einer Million Elementen also knapp eine Million Referenzen pro Aufruf. Wenn du oft am Anfang einfügst oder entfernst, nimm statt der ArrayList ein ArrayDeque. Es speichert seine Elemente in einem Ringpuffer und verschiebt beim Einfügen und Entfernen am Anfang keine Elemente.
Leere Collections
Bei einer leeren Collection werfen getFirst(), getLast(), removeFirst() und removeLast() eine NoSuchElementException. Das gilt auch für Listen, bei denen get(0) eine andere Exception wirft:
List<String> empty = new ArrayList<>();
try {
empty.get(0);
} catch (RuntimeException e) {
System.out.println(e);
}
try {
empty.getFirst();
} catch (RuntimeException e) {
System.out.println(e);
}
java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
java.util.NoSuchElementException
Eine Methode, die bei einer leeren Collection null liefert, hat SequencedCollection nicht. Deque hat dafür peekFirst() und peekLast(). Bei einer Liste solltest du vorher prüfen, ob sie leer ist:
List<String> empty = new ArrayList<>();
String first = empty.isEmpty() ? null : empty.getFirst();
System.out.println(first);
null
Unveränderliche Collections
addFirst(), addLast(), removeFirst() und removeLast() sind optionale Operationen. Eine unveränderliche Collection – etwa eine Liste aus List.of() – lehnt sie mit einer UnsupportedOperationException ab:
List<String> pioneers = List.of("Ada", "Grace", "Alan");
pioneers.addFirst("Charles");
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:156)
at java.base/java.util.ImmutableCollections$AbstractImmutableList.add(ImmutableCollections.java:272)
at java.base/java.util.List.addFirst(List.java:799)
Der Stacktrace zeigt, wie addFirst() bei einer Liste arbeitet: Die Default-Methode in List ruft add() mit dem Index 0 auf, und diesen Aufruf lehnt die unveränderliche Liste ab. getFirst(), getLast() und reversed() funktionieren dagegen auch bei unveränderlichen Collections.
reversed(): die umgekehrte Reihenfolge als View
Die siebte SequencedCollection-Methode, reversed(), liefert die Elemente in umgekehrter Reihenfolge – nicht als Kopie, sondern als sogenannte View: ein Objekt ohne eigene Elemente, das jeden Zugriff an die ursprüngliche Collection weiterreicht.
Das folgende Beispiel erzeugt eine solche View, ändert danach die ursprüngliche Liste und entfernt anschließend ein Element über die View:
List<String> pioneers = new ArrayList<>(List.of("Ada", "Grace", "Alan"));
List<String> reversed = pioneers.reversed();
System.out.println(reversed);
pioneers.add("Linus");
System.out.println(reversed);
reversed.removeFirst();
System.out.println(pioneers);
[Alan, Grace, Ada]
[Linus, Alan, Grace, Ada]
[Ada, Grace, Alan]
Das Element „Linus“, das das Programm an die ursprüngliche Liste hinten angehängt hat, steht in der View vorne. Umgekehrt entfernt removeFirst() auf der View das letzte Element der ursprünglichen Liste.
Da reversed() keine Elemente kopiert, kostet das Erzeugen der View unabhängig von der Größe der Liste gleich viel.
Rückwärts iterieren
Am häufigsten brauchst du reversed(), um eine Collection rückwärts zu durchlaufen. Bei einer Liste ging das bis Java 20 mit einem ListIterator, den du am Ende der Liste starten musstest:
List<String> pioneers = List.of("Ada", "Grace", "Alan");
ListIterator<String> iterator = pioneers.listIterator(pioneers.size());
while (iterator.hasPrevious()) {
System.out.println(iterator.previous());
}
Seit Java 21 genügt eine for-each-Schleife über reversed():
List<String> pioneers = List.of("Ada", "Grace", "Alan");
for (String pioneer : pioneers.reversed()) {
System.out.println(pioneer);
}
Beide Listings geben dasselbe aus:
Alan
Grace
Ada
Auch einen Stream kannst du so in umgekehrter Reihenfolge erzeugen:
List<String> pioneers = List.of("Ada", "Grace", "Alan");
List<String> upperCase = pioneers.reversed()
.stream()
.map(String::toUpperCase)
.toList();
System.out.println(upperCase);
[ALAN, GRACE, ADA]
reversed() vs. Collections.reverse() und descendingSet()
Für die umgekehrte Reihenfolge gab es schon vor Java 21 einige Methoden. Die folgende Tabelle zeigt, was sie liefern und ob sie die ursprüngliche Collection verändern:
| Aufruf | Liefert | Verändert das Original | Seit |
|---|---|---|---|
Collections.reverse(list) | nichts | ja | Java 1.2 |
navigableSet.descendingSet() | View | nein | Java 6 |
navigableMap.descendingMap() | View | nein | Java 6 |
deque.descendingIterator() | Iterator | nein | Java 6 |
Collections.reverse() dreht die Liste selbst um. Bei einem TreeSet und einer TreeMap ruft reversed() intern descendingSet() bzw. descendingMap() auf.
Der Sonderfall LinkedList
List.reversed() liefert eine List, Deque.reversed() ein Deque – jedes Interface überschreibt die Methode mit seinem eigenen Rückgabetyp. Eine Klasse, die beide Interfaces – List und Deque – implementiert, muss sich deshalb für einen Rückgabetyp entscheiden, der zugleich List und Deque ist.
Die folgende Klasse implementiert beide Interfaces, aber nicht die reversed()-Methode. Bis Java 20 kompilierte sie:
public abstract class ListDeque<E> extends AbstractList<E> implements Deque<E> {
}
Seit Java 21 lehnt der Compiler sie ab:
error: types Deque<E> and List<E> are incompatible;
both define reversed(), but with unrelated return types
Im JDK betrifft das die Klasse LinkedList: Sie implementiert List und Deque und überschreibt reversed() deshalb mit dem Rückgabetyp LinkedList:
LinkedList<String> pioneers = new LinkedList<>(List.of("Ada", "Grace", "Alan"));
LinkedList<String> reversed = pioneers.reversed();
System.out.println(reversed);
[Alan, Grace, Ada]
Wenn du in deinem Code eine eigene Klasse hast, die List und Deque implementiert, musst du beim Umstieg auf Java 21 ebenso reversed() überschreiben – wie LinkedList mit der eigenen Klasse als Rückgabetyp. Mehr über die Interfaces der LinkedList erfährst du im Artikel über die Java LinkedList.
SequencedSet: LinkedHashSet und TreeSet
SequencedSet erweitert Set und SequencedCollection. Weitere Methoden definiert es nicht. Es überschreibt nur reversed(), damit die zurückgegebene View ein SequencedSet ist und keine allgemeine SequencedCollection.
LinkedHashSet: addFirst() und addLast() verschieben
Ein Set enthält jedes Element nur einmal. Was passiert also, wenn du mit addFirst() oder addLast() ein Element einfügst, das schon im Set steht? Die Antwort: Das LinkedHashSet verschiebt es an den Anfang bzw. an das Ende. add() lässt die Reihenfolge dagegen unverändert, wie bis Java 20:
SequencedSet<String> tags =
new LinkedHashSet<>(List.of("java", "collections", "api"));
tags.add("java");
System.out.println(tags);
tags.addLast("java");
System.out.println(tags);
tags.addFirst("api");
System.out.println(tags);
[java, collections, api]
[collections, api, java]
[api, collections, java]
add("java") ändert nichts, denn „java“ steht schon im Set. addLast("java") verschiebt „java“ ans Ende, addFirst("api") verschiebt „api“ an den Anfang.
TreeSet: kein addFirst() und kein addLast()
Bei einem TreeSet bestimmt die Sortierung die Position jedes Elements. Lesen, Entfernen und reversed() funktionieren deshalb, Einfügen an einer bestimmten Position nicht:
SortedSet<Integer> versions = new TreeSet<>(List.of(21, 17, 25));
System.out.println(versions.getFirst());
System.out.println(versions.getLast());
System.out.println(versions.reversed());
System.out.println(versions.removeFirst());
System.out.println(versions);
versions.addFirst(11);
17
25
[25, 21, 17]
17
[21, 25]
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.TreeSet.addFirst(TreeSet.java:478)
getFirst() und getLast() liefern dasselbe wie die Methoden first() und last(), die SortedSet seit Java 1.2 hat. addFirst() und addLast() werfen bei jedem SortedSet eine UnsupportedOperationException. Laut JEP 431 gehört SortedSet trotzdem zur Familie der Sequenced Collections, damit du ein TreeSet überall verwenden kannst, wo eine SequencedCollection erwartet wird – wie bei der Methode printEnds() oben.
SequencedMap: LinkedHashMap und TreeMap
Maps stehen im Collections Framework in einer eigenen Hierarchie. Für Maps mit Encounter Order führt Java 21 das Interface SequencedMap ein. LinkedHashMap implementiert es direkt, TreeMap über SortedMap.
SequencedMap definiert die folgenden Methoden:
firstEntry()undlastEntry()liefern den ersten bzw. den letzten Eintrag der Map, bei einer leeren Mapnull.pollFirstEntry()undpollLastEntry()entfernen den ersten bzw. den letzten Eintrag und geben ihn zurück.putFirst(K, V)undputLast(K, V)fügen einen Eintrag am Anfang bzw. am Ende ein. Gibt es den Schlüssel schon, überschreiben sie seinen Wert und verschieben den Eintrag an den Anfang bzw. an das Ende.reversed()liefert eine View auf die Map in umgekehrter Reihenfolge.sequencedKeySet(),sequencedValues()undsequencedEntrySet()liefern die Schlüssel, die Werte bzw. die Einträge als Sequenced Collection.
Für TreeMap sind firstEntry(), lastEntry(), pollFirstEntry() und pollLastEntry() nicht neu – sie stehen seit Java 6 im Interface NavigableMap. Neu sind sie für die LinkedHashMap.
Beispiel: die zuletzt geöffneten Dateien
Ein typischer Fall für putLast() in einer Map ist eine Liste der zuletzt geöffneten Dateien, wie sie viele Editoren anzeigen. Die folgende Methode open() speichert zu jeder Datei, wie oft sie geöffnet wurde. Mit putLast() verschiebt sie die Datei ans Ende der Map, und wenn die Map mehr als drei Einträge hat, entfernt sie mit pollFirstEntry() den ältesten:
static void open(SequencedMap<String, Integer> recentFiles, String file) {
int count = recentFiles.getOrDefault(file, 0);
recentFiles.putLast(file, count + 1);
if (recentFiles.size() > 3) {
recentFiles.pollFirstEntry();
}
}
Das folgende Programm öffnet nacheinander vier Dateien, pom.xml davon zweimal, und gibt nach jedem Öffnen die Map aus:
SequencedMap<String, Integer> recentFiles = new LinkedHashMap<>();
List<String> files =
List.of("pom.xml", "Main.java", "README.md", "pom.xml", "Test.java");
for (String file : files) {
open(recentFiles, file);
System.out.println(file + " geöffnet --> " + recentFiles);
}
System.out.println("lastEntry() = " + recentFiles.lastEntry());
System.out.println("sequencedKeySet().reversed() = "
+ recentFiles.sequencedKeySet().reversed());
pom.xml geöffnet --> {pom.xml=1}
Main.java geöffnet --> {pom.xml=1, Main.java=1}
README.md geöffnet --> {pom.xml=1, Main.java=1, README.md=1}
pom.xml geöffnet --> {Main.java=1, README.md=1, pom.xml=2}
Test.java geöffnet --> {README.md=1, pom.xml=2, Test.java=1}
lastEntry() = Test.java=1
sequencedKeySet().reversed() = [Test.java, pom.xml, README.md]
Die vierte Zeile zeigt das zweite Öffnen von pom.xml: putLast() erhöht den Zähler auf 2 und verschiebt den Eintrag hinter README.md. Danach steht Main.java vorne. Beim Öffnen von Test.java hat die Map vier Einträge, und pollFirstEntry() entfernt Main.java – das zeigt die fünfte Zeile. lastEntry() liefert die zuletzt geöffnete Datei, und sequencedKeySet().reversed() liefert die Dateinamen von der jüngsten zur ältesten.
Mit put() statt putLast() würde pom.xml beim zweiten Öffnen an seiner Position bleiben. Das zeigt das folgende Beispiel:
SequencedMap<String, Integer> recentFiles = new LinkedHashMap<>();
recentFiles.put("pom.xml", 1);
recentFiles.put("Main.java", 1);
recentFiles.put("pom.xml", 2);
System.out.println(recentFiles);
recentFiles.putLast("pom.xml", 3);
System.out.println(recentFiles);
{pom.xml=2, Main.java=1}
{Main.java=1, pom.xml=3}
Eine TreeMap lehnt putFirst() und putLast() ab, denn wie beim TreeSet bestimmt die Sortierung die Position – hier die der Schlüssel:
SequencedMap<Integer, String> releases =
new TreeMap<>(Map.of(21, "2023", 17, "2021"));
System.out.println(releases.firstEntry());
releases.putFirst(11, "2018");
17=2021
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.TreeMap.putFirst(TreeMap.java:322)
Warum sequencedKeySet() und nicht keySet()?
Naheliegend wäre, dass keySet() bei einer LinkedHashMap seit Java 21 ein SequencedSet liefert. Der Rückgabetyp von keySet() ist aber weiterhin Set, und Set hat kein getLast():
LinkedHashMap<String, Integer> recentFiles = new LinkedHashMap<>();
recentFiles.put("pom.xml", 1);
System.out.println(recentFiles.keySet().getLast());
error: cannot find symbol
symbol: method getLast()
location: interface Set<String>
Eine frühere Fassung von JEP 431 hat keySet(), values() und entrySet() mit den Sequenced-Typen als Rückgabetyp überschrieben. Laut JEP hätte das aber jede bestehende Unterklasse von z. B. LinkedHashMap ungültig gemacht: Eine Unterklasse, die keySet() mit dem Rückgabetyp Set überschreibt, hätte nicht mehr kompiliert.
Deshalb gibt es die drei neuen Methoden mit dem Präfix sequenced: sequencedKeySet(), sequencedValues() und sequencedEntrySet(). Die Autoren von JEP 431 vermuten, dass NavigableMap in Java 6 aus demselben Grund die Methode navigableKeySet() bekam, statt keySet() zu überschreiben.
Unveränderliche Views aus der Klasse Collections
Für die drei neuen Interfaces hat die Klasse Collections vier neue Methoden bekommen:
unmodifiableSequencedCollection(),unmodifiableSequencedSet()undunmodifiableSequencedMap()liefern eine unveränderliche View auf eineSequencedCollection, einSequencedSetbzw. eineSequencedMap– analog zuunmodifiableList()und den anderenunmodifiable-Methoden, die es seit Java 1.2 gibt.newSequencedSetFromMap()liefert einSequencedSet, das seine Elemente als Schlüssel der übergebenenSequencedMapspeichert – analog zunewSetFromMap().
Lesende Methoden wie getLast() und reversed() funktionieren auf einer unveränderlichen View, ändernde Methoden werfen eine UnsupportedOperationException:
List<String> pioneers = new ArrayList<>(List.of("Ada", "Grace"));
SequencedCollection<String> view =
Collections.unmodifiableSequencedCollection(pioneers);
System.out.println(view.getLast());
System.out.println(view.reversed());
view.removeLast();
Grace
[Grace, Ada]
Exception in thread "main" java.lang.UnsupportedOperationException
Vorher und nachher
Die folgende Tabelle stellt für die häufigsten Aufgaben den Code bis Java 20 dem Code seit Java 21 gegenüber:
| Aufgabe | Bis Java 20 | Seit Java 21 |
|---|---|---|
| erstes Element einer Liste | list.get(0) | list.getFirst() |
| letztes Element einer Liste | list.get(list.size() - 1) | list.getLast() |
erstes Element eines LinkedHashSet | set.iterator().next() | set.getFirst() |
letztes Element eines LinkedHashSet | Schleife über alle Elemente | set.getLast() |
| Liste rückwärts durchlaufen | ListIterator ab dem Ende | list.reversed() |
ältester Eintrag einer LinkedHashMap | map.entrySet().iterator().next() | map.firstEntry() |
jüngster Eintrag einer LinkedHashMap | Schleife über alle Einträge | map.lastEntry() |
| Eintrag ans Ende verschieben | map.remove(k), map.put(k, v) | map.putLast(k, v) |
Fallstricke
reversed() ist keine Kopie
Weil reversed() eine View liefert, ändert sie sich mit der ursprünglichen Collection. Speicherst du sie als Momentaufnahme und leerst die Collection danach, ist auch die View leer:
List<String> pioneers = new ArrayList<>(List.of("Ada", "Grace", "Alan"));
List<String> backwards = pioneers.reversed();
pioneers.clear();
System.out.println(backwards);
[]
Brauchst du eine Kopie in umgekehrter Reihenfolge, kopiere die View:
List<String> pioneers = new ArrayList<>(List.of("Ada", "Grace", "Alan"));
List<String> backwards = new ArrayList<>(pioneers.reversed());
pioneers.clear();
System.out.println(backwards);
[Alan, Grace, Ada]
Die Verbindung zwischen View und ursprünglicher Collection gilt auch in umgekehrter Richtung: Änderst du die View, änderst du auch die ursprüngliche Collection. Im Beispiel weiter oben hat removeFirst() auf der View das letzte Element der ursprünglichen Liste entfernt.
firstEntry() liefert einen unveränderlichen Eintrag
firstEntry() und lastEntry() liefern eine unveränderliche Kopie des Eintrags. Seinen Wert kannst du über diese Kopie nicht ändern:
SequencedMap<String, Integer> recentFiles = new LinkedHashMap<>();
recentFiles.put("pom.xml", 1);
recentFiles.firstEntry().setValue(2);
Exception in thread "main" java.lang.UnsupportedOperationException: not supported
Die Einträge von sequencedEntrySet() sind dagegen mit der Map verbunden, genau wie die von entrySet(). Über sie kannst du den Wert ändern:
SequencedMap<String, Integer> recentFiles = new LinkedHashMap<>();
recentFiles.put("pom.xml", 1);
recentFiles.sequencedEntrySet().getFirst().setValue(2);
System.out.println(recentFiles);
{pom.xml=2}
Leere Map: null statt Exception
Bei einer leeren Collection werfen getFirst() und getLast() eine NoSuchElementException. Bei einer leeren Map liefern firstEntry(), lastEntry(), pollFirstEntry() und pollLastEntry() dagegen null, wie die gleichnamigen Methoden von NavigableMap seit Java 6. Die drei Views der Map – sequencedKeySet(), sequencedValues() und sequencedEntrySet() – sind wiederum Sequenced Collections und werfen bei einer leeren Map eine NoSuchElementException:
SequencedMap<String, Integer> recentFiles = new LinkedHashMap<>();
System.out.println(recentFiles.firstEntry());
try {
recentFiles.sequencedKeySet().getFirst();
} catch (RuntimeException e) {
System.out.println(e);
}
null
java.util.NoSuchElementException
Prüfe das Ergebnis von firstEntry() und lastEntry() deshalb auf null, bevor du getKey() oder getValue() aufrufst.
Zusammenfassung
Seit Java 21 haben alle Collections mit festgelegter Reihenfolge – Listen, Deques, LinkedHashSet, TreeSet und ihre Gegenstücke bei den Maps – gemeinsame Methoden für das erste und das letzte Element und für die umgekehrte Reihenfolge. Sie stammen aus den Interfaces SequencedCollection, SequencedSet und SequencedMap, die durch JEP 431 eingeführt wurden.
getFirst() und getLast() werfen bei einer leeren Collection eine NoSuchElementException, firstEntry() und lastEntry() liefern bei einer leeren Map null. reversed() liefert eine View, keine Kopie. Ein LinkedHashSet und eine LinkedHashMap verschieben ein vorhandenes Element bei addFirst() und addLast() bzw. putFirst() und putLast(). TreeSet und TreeMap lehnen diese Methoden ab, weil bei ihnen die Sortierung die Position bestimmt.
Vier Empfehlungen für deinen Code:
- Ersetze
list.get(0)undlist.get(list.size() - 1)durchgetFirst()undgetLast()– und durchlaufe Collections rückwärts mitreversed()statt mit einemListIterator. - Deklariere einen Parameter als
SequencedCollection, wenn deine Methode eine Reihenfolge braucht, aber nicht auf einen bestimmten Collection-Typ angewiesen ist. - Kopiere das Ergebnis von
reversed(), wenn du eine Momentaufnahme brauchst. - Nimm ein
ArrayDequestatt einerArrayList, wenn du oft am Anfang einfügst oder entfernst.
Welche weiteren Neuerungen Java 21 gebracht hat, erfährst du im Artikel über die Java 21 Features. Wie Deque und Queue zusammenhängen, steht im Artikel Queue vs. Deque.
Wenn dir der Artikel weitergeholfen hat, würde ich mich sehr über eine positive Bewertung auf meinem ProvenExpert-Profil freuen. Dein Feedback hilft mir, meine Inhalte weiter zu verbessern und motiviert mich, neue informative Artikel zu schreiben.




