PrepZone Logo
PrepZone

Sequenced Collections

One vocabulary for first, last and reversed across every ordered collection.

Why this matters

  • Getting the last element used to require a different expression for every collection type, and one of those expressions was a four-line workaround.
  • It is a rare retrofit of the collections framework, so it shows up in "what is new in Java 21" conversations alongside virtual threads.
  • reversed() gives you a view rather than a copy, which is both convenient and a source of surprises if you do not know it.

The problem

Java
// Before Java 21 — first and last, four different ways
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
String firstOfList = list.get(0);
String lastOfList = list.get(list.size() - 1);                 // and an exception if empty

Deque<String> deque = new ArrayDeque<>(list);
String firstOfDeque = deque.getFirst();
String lastOfDeque = deque.getLast();

SortedSet<String> sorted = new TreeSet<>(list);
String firstOfSorted = sorted.first();
String lastOfSorted = sorted.last();

LinkedHashSet<String> ordered = new LinkedHashSet<>(list);
String firstOfOrdered = ordered.iterator().next();
String lastOfOrdered = null;
for (String value : ordered) { lastOfOrdered = value; }         // a full traversal for the last element

Four collections with a well-defined order, four different vocabularies, and one of them needs a loop.

The new interfaces

Java
interface SequencedCollection<E> extends Collection<E> {
    SequencedCollection<E> reversed();
    void addFirst(E e);
    void addLast(E e);
    E getFirst();
    E getLast();
    E removeFirst();
    E removeLast();
}

interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
    SequencedSet<E> reversed();
}

interface SequencedMap<K, V> extends Map<K, V> {
    SequencedMap<K, V> reversed();
    Map.Entry<K, V> firstEntry();
    Map.Entry<K, V> lastEntry();
    Map.Entry<K, V> pollFirstEntry();
    Map.Entry<K, V> pollLastEntry();
    V putFirst(K k, V v);
    V putLast(K k, V v);
    SequencedSet<K> sequencedKeySet();
    SequencedCollection<V> sequencedValues();
    SequencedSet<Map.Entry<K, V>> sequencedEntrySet();
}
SequencedCollection
getFirst() / getLast()Read either end
addFirst() / addLast()Write either end
removeFirst() / removeLast()Take from either end
reversed()A view, not a copy

Implemented by List, LinkedHashSet, SortedSet, LinkedHashMap and SortedMap.

Any collection with a defined order now shares the same vocabulary for both ends, replacing the old mix of getFirst, iterator().next() and listIterator tricks.

Where they slot into the hierarchy

  • SequencedCollection is implemented by List, Deque, SortedSet and LinkedHashSet.
  • SequencedSet sits between Set and the ordered set implementations: LinkedHashSet, TreeSet and any SortedSet.
  • SequencedMap is implemented by LinkedHashMap, TreeMap and any SortedMap.
  • HashSet and HashMap are excluded, because they have no defined encounter order — which is exactly the point of the split.
Java
// After Java 21 — one vocabulary everywhere
list.getFirst();           list.getLast();
deque.getFirst();          deque.getLast();
linkedHashSet.getFirst();  linkedHashSet.getLast();
treeSet.getFirst();        treeSet.getLast();
linkedHashMap.firstEntry();linkedHashMap.lastEntry();

reversed() is a view

Java
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
List<String> backwards = list.reversed();

System.out.println(backwards);                  // [c, b, a]

list.add("d");
System.out.println(backwards);                   // [d, c, b, a] — the view sees the change

backwards.set(0, "z");                           // writes through
System.out.println(list);                         // [a, b, c, z]
Java
// Iterating backwards no longer needs an index loop or Collections.reverse
for (String value : list.reversed()) {
    process(value);
}

// A reversed snapshot, when you do want an independent copy
List<String> copy = List.copyOf(list.reversed());

SequencedMap in practice

Java
LinkedHashMap<String, Integer> scores = new LinkedHashMap<>();
scores.put("ana", 90);
scores.put("bo", 85);
scores.put("cy", 78);

scores.firstEntry();                    // ana=90
scores.lastEntry();                     // cy=78
scores.pollFirstEntry();                // removes and returns ana=90

scores.putFirst("dee", 99);             // inserted at the front, reordering if the key exists
scores.putLast("ana", 91);              // moved to the end

for (var entry : scores.reversed().entrySet()) { }    // newest first

scores.sequencedKeySet().getFirst();    // ordered key view

An LRU cache, before and after

Java
// Before: access-order LinkedHashMap with an overridden removeEldestEntry
class LruCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;

    LruCache(int capacity) {
        super(16, 0.75f, true);                       // true = access order
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }
}
Java
// After: the eviction step is explicit rather than a protected hook
class LruCache<K, V> {
    private final LinkedHashMap<K, V> map = new LinkedHashMap<>();
    private final int capacity;

    LruCache(int capacity) { this.capacity = capacity; }

    V get(K key) {
        V value = map.remove(key);
        if (value != null) map.putLast(key, value);     // refresh the position
        return value;
    }

    void put(K key, V value) {
        map.putLast(key, value);
        if (map.size() > capacity) map.pollFirstEntry();  // evict the least recent
    }
}

The second version is longer but says what it does. Both are correct; the point is that the ordering operations are now part of the public API rather than something you reach for a protected method to influence.

Compatibility notes

AspectWhat is safeWhat to watch
Existing codeUnchanged — these are additionsA class implementing List must now satisfy the new default methods
Unmodifiable collectionsgetFirst and getLast workaddFirst, putFirst and removal throw UnsupportedOperationException
TreeMap and TreeSetgetFirst, getLast and reversed workaddFirst, addLast, putFirst, putLast throw — order is the comparator's
Empty collectionsBehaviour is definedgetFirst and getLast throw NoSuchElementException, not null
NamingReads the same across every typeList.getFirst() and SortedSet.first() both still exist
  • Existing code

    What is safeUnchanged — these are additions
    What to watchA class implementing List must now satisfy the new default methods
  • Unmodifiable collections

    What is safegetFirst and getLast work
    What to watchaddFirst, putFirst and removal throw UnsupportedOperationException
  • TreeMap and TreeSet

    What is safegetFirst, getLast and reversed work
    What to watchaddFirst, addLast, putFirst, putLast throw — order is the comparator's
  • Empty collections

    What is safeBehaviour is defined
    What to watchgetFirst and getLast throw NoSuchElementException, not null
  • Naming

    What is safeReads the same across every type
    What to watchList.getFirst() and SortedSet.first() both still exist

The additions are source-compatible for callers, and only affect you if you implement the interfaces yourself.

Common misreadings

  • "HashMap is sequenced." It is not, deliberately — it has no defined encounter order.
  • "reversed() returns a copy." It is a live, writable view in both directions.
  • "reversed() and Collections.reverse() are the same." The latter reverses in place and returns nothing.
  • "putFirst is just put." It also repositions an existing key.
  • "addFirst works on every sequenced collection." TreeSet, TreeMap and unmodifiable collections throw.
  • "getFirst() returns null when empty." It throws NoSuchElementException.
  • "The old methods were removed." first(), last() and get(0) all still work.

Quick recall

Everything you need if you only revisit this box.

  • Three new interfaces: SequencedCollection, SequencedSet, SequencedMap — one vocabulary for ordered collections.
  • getFirst, getLast, addFirst, addLast, removeFirst, removeLast, reversed(); on maps, firstEntry, lastEntry, pollFirstEntry, pollLastEntry, putFirst, putLast.
  • Implemented by List, Deque, LinkedHashSet, SortedSet/TreeSet, LinkedHashMap, SortedMap/TreeMap. Not HashSet or HashMap.
  • reversed() is a live two-way view, unlike Collections.reverse, which reverses in place.
  • putFirst/putLast reposition an existing key; plain put does not.
  • Comparator-ordered and unmodifiable collections throw UnsupportedOperationException for the positional add and put methods.
  • getFirst()/getLast() on an empty collection throw NoSuchElementException.

Test yourself

Answer these before moving on — recall is what makes it stick.