Sequenced Collections
Use the Java 21 SequencedCollection and SequencedMap methods to read, add and remove at both ends and to walk any ordered collection in reverse.
list.getFirst(); list.getLast();
list.addFirst(x); list.removeLast();
List<E> newestFirst = list.reversed();
map.firstEntry(); map.pollFirstEntry(); map.putFirst(k, v);Java 21 added three interfaces for collections with a defined encounter order: SequencedCollection, SequencedSet and SequencedMap. Before them, getting the last element meant list.get(list.size() - 1) for a list, peekLast() for a deque and a full loop for a LinkedHashSet. Now one set of methods works on all of them.
The methods
SequencedCollection declares:
getFirst(),getLast()addFirst(e),addLast(e)removeFirst(),removeLast()reversed(), a reverse-ordered view
SequencedMap adds firstEntry(), lastEntry(), pollFirstEntry(), pollLastEntry(), putFirst, putLast, reversed(), and the ordered views sequencedKeySet(), sequencedValues() and sequencedEntrySet().
Who implements them
| Interface | Types |
|---|---|
SequencedCollection | List, Deque and every SequencedSet |
SequencedSet | LinkedHashSet, SortedSet (so TreeSet) |
SequencedMap | LinkedHashMap, SortedMap (so TreeMap) |
HashSet and HashMap have no defined order, so they do not implement these interfaces.
Details worth knowing
getFirst()on an empty collection throwsNoSuchElementException, whileget(0)on an empty list throwsIndexOutOfBoundsException. CheckisEmpty()first when empty is a normal case.reversed()copies nothing. Changes to the original show through the view, and writes through a modifiable view change the original. ReturnList.copyOf(list.reversed())if callers must not see later changes.- Sorted collections decide their own order, so
addFirstandaddLaston aTreeSet, andputFirston aTreeMap, throwUnsupportedOperationException. Unmodifiable lists throw the same for every mutator. - On a
LinkedHashSet,addFirstwith an element already present moves it to the front, andputFirston aLinkedHashMapmoves an existing key in the same way.
Back-end angle
Typical uses are reading the latest event of an order's history, returning a newest-first list without copying and re-sorting, keeping a short list of recently viewed items with the most recent at the front, and taking the oldest pending entry from a LinkedHashMap with pollFirstEntry(). Methods can now accept SequencedCollection<T> when they need order but not indexes.
Example
When to use it
- Read the latest status of an order with history.getLast() instead of indexing by size minus one.
- Return an audit trail newest first with reversed() without copying or re-sorting the list.
- Keep a de-duplicated recently viewed products list per session with LinkedHashSet.addFirst.
More examples
reversed() is a live view
The view and the original share the same data in both directions. Copy the reversed view when you need a value that stays fixed.
void main() {
List<Integer> ids = new ArrayList<>(List.of(1, 2, 3));
List<Integer> newestFirst = ids.reversed();
ids.add(4);
IO.println("view after ids.add(4): " + newestFirst);
newestFirst.addFirst(5); // the front of the view is the end of ids
IO.println("ids after view.addFirst(5): " + ids);
List<Integer> snapshot = List.copyOf(ids.reversed());
ids.add(6);
IO.println("snapshot: " + snapshot);
}
// Output:
// view after ids.add(4): [4, 3, 2, 1]
// ids after view.addFirst(5): [1, 2, 3, 4, 5]
// snapshot: [5, 4, 3, 2, 1]Recently viewed items and sorted sets
addFirst on a LinkedHashSet both de-duplicates and moves the item to the front, which is exactly what a recently viewed list needs. A TreeSet still gains getFirst and getLast but rejects positional inserts.
void main() {
LinkedHashSet<String> recent = new LinkedHashSet<>(List.of("/p/1", "/p/2", "/p/3"));
recent.addFirst("/p/2"); // already present: moved to the front
IO.println("recent: " + recent);
IO.println("oldest: " + recent.getLast());
TreeSet<Integer> scores = new TreeSet<>(List.of(70, 95, 82));
IO.println("lowest " + scores.getFirst() + ", highest " + scores.getLast());
try {
scores.addFirst(100);
} catch (UnsupportedOperationException e) {
IO.println("TreeSet keeps its own order, so addFirst is unsupported");
}
}
// Output:
// recent: [/p/2, /p/1, /p/3]
// oldest: /p/3
// lowest 70, highest 95
// TreeSet keeps its own order, so addFirst is unsupportedEmpty collections and draining a SequencedMap
pollFirstEntry removes and returns the oldest entry in one call. The two exceptions differ, so a catch block written for get(0) will not catch getFirst.
void main() {
List<String> empty = new ArrayList<>();
try {
empty.getFirst();
} catch (NoSuchElementException e) {
IO.println("getFirst on empty: NoSuchElementException");
}
try {
empty.get(0);
} catch (IndexOutOfBoundsException e) {
IO.println("get(0) on empty: IndexOutOfBoundsException");
}
SequencedMap<String, String> pending = new LinkedHashMap<>();
pending.put("req-1", "GET /users");
pending.put("req-2", "POST /orders");
pending.put("req-3", "GET /health");
var oldest = pending.pollFirstEntry();
IO.println("handled " + oldest.getKey() + " " + oldest.getValue());
IO.println("left: " + pending.sequencedKeySet());
IO.println("newest first: " + pending.reversed());
}
// Output:
// getFirst on empty: NoSuchElementException
// get(0) on empty: IndexOutOfBoundsException
// handled req-1 GET /users
// left: [req-2, req-3]
// newest first: {req-3=GET /health, req-2=POST /orders}
Discussion