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
// 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
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();
}
Implemented by List, LinkedHashSet, SortedSet, LinkedHashMap and SortedMap.
Where they slot into the hierarchy
SequencedCollectionis implemented byList,Deque,SortedSetandLinkedHashSet.SequencedSetsits betweenSetand the ordered set implementations:LinkedHashSet,TreeSetand anySortedSet.SequencedMapis implemented byLinkedHashMap,TreeMapand anySortedMap.HashSetandHashMapare excluded, because they have no defined encounter order — which is exactly the point of the split.
// 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
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]
// 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
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
// 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;
}
}
// 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
| Aspect | What is safe | What to watch |
|---|---|---|
| Existing code | Unchanged — these are additions | A class implementing List must now satisfy the new default methods |
| Unmodifiable collections | getFirst and getLast work | addFirst, putFirst and removal throw UnsupportedOperationException |
| TreeMap and TreeSet | getFirst, getLast and reversed work | addFirst, addLast, putFirst, putLast throw — order is the comparator's |
| Empty collections | Behaviour is defined | getFirst and getLast throw NoSuchElementException, not null |
| Naming | Reads the same across every type | List.getFirst() and SortedSet.first() both still exist |
Existing code
What is safeUnchanged — these are additionsWhat to watchA class implementing List must now satisfy the new default methodsUnmodifiable collections
What is safegetFirst and getLast workWhat to watchaddFirst, putFirst and removal throw UnsupportedOperationExceptionTreeMap and TreeSet
What is safegetFirst, getLast and reversed workWhat to watchaddFirst, addLast, putFirst, putLast throw — order is the comparator'sEmpty collections
What is safeBehaviour is definedWhat to watchgetFirst and getLast throw NoSuchElementException, not nullNaming
What is safeReads the same across every typeWhat 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
- "
HashMapis 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()andCollections.reverse()are the same." The latter reverses in place and returns nothing. - "
putFirstis justput." It also repositions an existing key. - "
addFirstworks on every sequenced collection."TreeSet,TreeMapand unmodifiable collections throw. - "
getFirst()returns null when empty." It throwsNoSuchElementException. - "The old methods were removed."
first(),last()andget(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. NotHashSetorHashMap. reversed()is a live two-way view, unlikeCollections.reverse, which reverses in place.putFirst/putLastreposition an existing key; plainputdoes not.- Comparator-ordered and unmodifiable collections throw
UnsupportedOperationExceptionfor the positional add and put methods. getFirst()/getLast()on an empty collection throwNoSuchElementException.
Test yourself
Answer these before moving on — recall is what makes it stick.
- What are Sequenced Collections (Java 21)? What interfaces were introduced and why?
- What are the major features introduced in Java 21 LTS (virtual threads, sequenced collections, record patterns)?
- What are collection factory methods (List.of, Set.of, Map.of) introduced in Java 9? How do they differ from Arrays.asList?