Contents
- The Problem Before Java 21
- The New Interfaces
- SequencedCollection
- SequencedMap
- Which Collections Implement These
Before Java 21, there was no consistent way to access the first or last element of an ordered collection. Each collection type had its own awkward workaround — get(size()-1) for List, iterating to the end for LinkedHashMap, or calling iterator().next() for LinkedHashSet. None of these were readable, and some were O(n).
// Getting last element of a List — no clean API
List<String> list = List.of("a", "b", "c");
// Before Java 21 — awkward
String last = list.get(list.size() - 1); // IndexOutOfBoundsException if empty
// Getting first/last of a LinkedHashMap — even worse
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
// Getting first entry — very awkward
Map.Entry<String, Integer> first = map.entrySet().iterator().next();
// Getting last entry — genuinely painful
Map.Entry<String, Integer> last = null;
for (Map.Entry<String, Integer> e : map.entrySet()) last = e; // O(n)!
// or: ((LinkedHashMap<String,Integer>) map).sequencedEntrySet().last()
// ... which didn't exist before Java 21
// Getting first/last of a LinkedHashSet
LinkedHashSet<String> set = new LinkedHashSet<>(List.of("x","y","z"));
String firstElem = set.iterator().next(); // only way before Java 21
- SequencedCollection<E> — extends Collection<E>: adds getFirst(), getLast(), addFirst(), addLast(), removeFirst(), removeLast(), reversed().
- SequencedSet<E> — extends SequencedCollection<E> and Set<E>: reversed() returns a SequencedSet.
- SequencedMap<K,V> — extends Map<K,V>: adds firstEntry(), lastEntry(), pollFirstEntry(), pollLastEntry(), putFirst(), putLast(), reversed(), sequencedKeySet(), sequencedValues(), sequencedEntrySet().
SequencedCollection is the base interface extended by List, Deque, and LinkedHashSet. It adds getFirst(), getLast(), addFirst(), addLast(), removeFirst(), removeLast(), and reversed() — a reversed view of the same underlying collection (not a copy).
// List implements SequencedCollection
List<String> list = new ArrayList<>(List.of("a", "b", "c", "d"));
// Clean first/last access
String first = list.getFirst(); // "a"
String last = list.getLast(); // "d"
// Add/remove at ends
list.addFirst("Z"); // ["Z", "a", "b", "c", "d"]
list.addLast("X"); // ["Z", "a", "b", "c", "d", "X"]
list.removeFirst(); // removes "Z"
list.removeLast(); // removes "X"
// reversed() — returns a view in reverse order (not a copy)
List<String> rev = list.reversed();
System.out.println(rev); // [d, c, b, a]
rev.forEach(System.out::println); // iterates in reverse
// Deque also implements SequencedCollection
Deque<Integer> deque = new ArrayDeque<>(List.of(1, 2, 3));
System.out.println(deque.getFirst()); // 1
System.out.println(deque.getLast()); // 3
// LinkedHashSet implements SequencedSet
LinkedHashSet<String> set = new LinkedHashSet<>(List.of("x", "y", "z"));
System.out.println(set.getFirst()); // "x"
System.out.println(set.getLast()); // "z"
SequencedSet<String> revSet = set.reversed();
System.out.println(revSet.getFirst()); // "z"
getFirst() and getLast() throw NoSuchElementException if the collection is empty — unlike the old get(0) which throws IndexOutOfBoundsException.
SequencedMap extends Map and is implemented by LinkedHashMap, TreeMap, and NavigableMap. It adds firstEntry(), lastEntry(), pollFirstEntry(), pollLastEntry(), putFirst(), putLast(), and reversed(), as well as sequencedKeySet(), sequencedValues(), and sequencedEntrySet() for ordered iteration.
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
// First and last entries — clean!
Map.Entry<String, Integer> first = map.firstEntry(); // one=1
Map.Entry<String, Integer> last = map.lastEntry(); // three=3
System.out.println(first.getKey() + "=" + first.getValue()); // one=1
System.out.println(last.getKey() + "=" + last.getValue()); // three=3
// Poll (retrieve and remove)
Map.Entry<String, Integer> polled = map.pollFirstEntry(); // removes "one"
System.out.println(map.size()); // 2
// putFirst/putLast — insert at beginning/end
map.putFirst("zero", 0); // {zero=0, two=2, three=3}
map.putLast("four", 4); // {zero=0, two=2, three=3, four=4}
// reversed() — reversed view
SequencedMap<String, Integer> rev = map.reversed();
System.out.println(rev.firstEntry()); // four=4
// Sequenced views — for iteration in insertion order
SequencedCollection<String> keys = map.sequencedKeySet();
SequencedCollection<Integer> values = map.sequencedValues();
SequencedCollection<Map.Entry<String,Integer>> entries = map.sequencedEntrySet();
- SequencedCollection: List (ArrayList, LinkedList, Vector, Stack), Deque (ArrayDeque, LinkedList), LinkedHashSet, SortedSet (TreeSet).
- SequencedSet: LinkedHashSet, SortedSet (TreeSet), NavigableSet.
- SequencedMap: LinkedHashMap, SortedMap (TreeMap), NavigableMap.
- Not included: HashSet, HashMap, PriorityQueue — these have no defined encounter order.
// TreeMap (SortedMap → SequencedMap) — natural key order
TreeMap<String, Integer> treeMap = new TreeMap<>();
treeMap.put("banana", 2);
treeMap.put("apple", 1);
treeMap.put("cherry", 3);
System.out.println(treeMap.firstEntry()); // apple=1 (alphabetical)
System.out.println(treeMap.lastEntry()); // cherry=3
// Useful pattern: last-N elements from a sorted map
TreeMap<Long, String> events = new TreeMap<>();
// ... populated with timestamp → event ...
// Get last 3 events:
events.reversed().sequencedEntrySet().stream().limit(3)
.forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));