Sequenced Collections

2 minute read

Before Java 21, accessing the first or last element of a collection was inconsistent across types. Sequenced collections (Java 21) fix this with a unified interface.


The Problem

// Before Java 21 — different API for the same concept
// First element:
list.get(0);                          // List
deque.getFirst();                     // Deque
set.iterator().next();                // SortedSet — verbose!
map.firstEntry();                     // SortedMap

// Last element:
list.get(list.size() - 1);           // List — awkward
deque.getLast();                      // Deque
// SortedSet? LinkedHashSet? — no direct method!
map.lastEntry();                     // SortedMap

Every collection had its own way — or no way at all.


The Solution: Three New Interfaces

                   SequencedCollection<E>
                   ├── getFirst()
                   ├── getLast()
                   ├── addFirst(E)
                   ├── addLast(E)
                   ├── removeFirst()
                   ├── removeLast()
                   └── reversed()
                          │
              ┌───────────┴───────────┐
    SequencedSet<E>          SequencedMap<K,V>
    (no duplicates)          ├── firstEntry()
                             ├── lastEntry()
                             ├── pollFirstEntry()
                             ├── pollLastEntry()
                             ├── putFirst(K,V)
                             ├── putLast(K,V)
                             ├── reversed()
                             └── sequencedKeySet()
                                 sequencedValues()
                                 sequencedEntrySet()

Which Classes Implement What

Class Implements
ArrayList SequencedCollection
LinkedList SequencedCollection
ArrayDeque SequencedCollection
LinkedHashSet SequencedSet
TreeSet SequencedSet
LinkedHashMap SequencedMap
TreeMap SequencedMap

SequencedCollection in Action

// Works the same for ArrayList, LinkedList, ArrayDeque, etc.
SequencedCollection<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));

names.getFirst();    // "Alice"
names.getLast();     // "Charlie"

names.addFirst("Zara");   // ["Zara", "Alice", "Bob", "Charlie"]
names.addLast("Dan");     // ["Zara", "Alice", "Bob", "Charlie", "Dan"]

names.removeFirst();       // removes "Zara"
names.removeLast();        // removes "Dan"

// Reversed view (not a copy!)
SequencedCollection<String> reversed = names.reversed();
reversed.getFirst();    // "Charlie"
reversed.getLast();     // "Alice"

// Iterate in reverse
for (String name : names.reversed()) {
    System.out.println(name);
}

SequencedSet

SequencedSet<String> set = new LinkedHashSet<>();
set.add("Apple");
set.add("Banana");
set.add("Cherry");

set.getFirst();   // "Apple"
set.getLast();    // "Cherry"
set.reversed();   // ["Cherry", "Banana", "Apple"]

SequencedMap

SequencedMap<String, Integer> map = new LinkedHashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);

map.firstEntry();         // a=1
map.lastEntry();          // c=3

map.pollFirstEntry();     // removes and returns a=1
map.pollLastEntry();      // removes and returns c=3

map.putFirst("z", 26);   // insert at beginning
map.putLast("m", 13);    // insert at end

// Reversed
map.reversed().forEach((k, v) -> System.out.println(k + "=" + v));

// Sequenced views
map.sequencedKeySet();    // SequencedSet<String>
map.sequencedValues();    // SequencedCollection<Integer>
map.sequencedEntrySet();  // SequencedSet<Map.Entry<String, Integer>>

reversed() — A View, Not a Copy

var list = new ArrayList<>(List.of(1, 2, 3));
var rev = list.reversed();

rev.getFirst();     // 3
list.add(4);
rev.getFirst();     // 4 — reflects changes!

rev.addFirst(99);   // adds 99 at the END of original list
list;               // [1, 2, 3, 4, 99]

reversed() returns a live view — changes propagate both ways.


Before/After Comparison

Operation Before Java 21 Java 21+
First of List list.get(0) list.getFirst()
Last of List list.get(list.size()-1) list.getLast()
First of Set set.iterator().next() set.getFirst()
Reverse iterate ListIterator / copy + reverse collection.reversed()
First of Map map.entrySet().iterator().next() map.firstEntry()

Common Mistakes

Mistake Problem Fix
Calling getFirst() on empty collection NoSuchElementException Check isEmpty() first
Treating reversed() as a copy Modifications reflect back Be aware it’s a view
Using addFirst() on unmodifiable collections UnsupportedOperationException Use on mutable collections only
Calling addFirst() on TreeSet UnsupportedOperationException — order is natural Use LinkedHashSet for insertion-order

Key Takeaways

  1. SequencedCollection adds getFirst(), getLast(), reversed() to all ordered collections.
  2. SequencedSet and SequencedMap extend the concept to sets and maps.
  3. reversed() returns a live view — not a copy.
  4. ArrayList, LinkedHashSet, LinkedHashMap, TreeSet, TreeMap all implement it.
  5. Consistency — one API for first/last access across all collection types.

Module Complete

You’ve completed the Modern Java (8 → 21) module! You now know:

  • Java 8 foundations (lambdas, streams, Optional)
  • Java 11 additions (var, HttpClient, String methods)
  • Java 14–16 innovations (records, text blocks, pattern matching)
  • Java 17 LTS (sealed classes, strong encapsulation)
  • Java 21 LTS (virtual threads, switch patterns, sequenced collections)
  • Deep dives on text blocks, switch expressions, sealed classes, and structured concurrency

Updated: