Sequenced collections in Java
Java 21 getFirst, getLast, reversed() on List, Deque, LinkedHashSet, LinkedHashMap.
Goodbye size() - 1
Java 21 added **SequencedCollection: collections with a defined encounter order got one uniform API. getFirst, getLast, addFirst, addLast, removeFirst, removeLast and reversed()**.
var l = new ArrayList<>(List.of("a", "b"));
l.getFirst(); // "a" (was l.get(0))
l.getLast(); // "b"
// was l.get(l.size() - 1)Who joined the club
List, Deque, LinkedHashSet, SortedSet (TreeSet), LinkedHashMap and SortedMap (TreeMap) are all sequenced. HashSet and HashMap are not: they have no defined encounter order, so "first" would mean nothing.
Your turn
What does this print?
var list = new ArrayList<>(List.of("a", "b"));
var rev = list.reversed();
list.add("c");
System.out.println(rev);[b, a][c, b, a][a, b, c]
Show the answer
**reversed() returns a live view**, not a copy. The later add("c") shows up in it, at the front of the reversed order: [c, b, a].
Sequenced maps
**SequencedMap** brings firstEntry(), lastEntry(), pollFirstEntry(), plus **putFirst and putLast**, which place a mapping at the front or back of a LinkedHashMap's order.
var m = new LinkedHashMap<String, Integer>();
m.put("a", 1);
m.putFirst("z", 0); // {z=0, a=1}
m.lastEntry(); // a=1Jump the queue
What does this print?
var m = new LinkedHashMap<String, Integer>();
m.put("a", 1);
m.put("b", 2);
m.putFirst("c", 3);
var f = m.firstEntry();
System.out.println(f + " " + m.lastEntry());a=1 b=2c=3 b=2c=3 a=1
Show the answer
putFirst inserts c=3 at the front of the encounter order. The last entry is still b=2.
Empty means exception
On an empty collection, **getFirst() throws NoSuchElementException**, following the Deque convention. The old get(0) throws IndexOutOfBoundsException instead. Neither returns null.
List<String> empty = new ArrayList<>();
empty.getFirst(); // NoSuchElementException
empty.get(0); // IndexOutOfBoundsExceptionCleaner code
"Latest message", "oldest log entry", "most recently used item": these are now getLast(), getFirst() and reversed() one-liners. Fewer off-by-one errors, and code that works the same whether the collection is a List, a Deque or a LinkedHashSet.
Key takeaways
- list.getFirst() replaces list.get(0); getLast() replaces get(size() - 1)
- reversed() returns a reverse-ordered VIEW, not a copy
- SequencedMap adds firstEntry, lastEntry, putFirst, putLast
- On an empty collection, getFirst() throws NoSuchElementException
Sequenced collections arrived via JEP 431 in Java 21. Before them, the only way to get the last element of a LinkedHashSet was to iterate through every element.
Practice questions
What does this print?
var list = new ArrayList<>(List.of(1, 2, 3));
var rev = list.reversed();
list.add(4);
System.out.println(rev);- [3, 2, 1]
- [4, 3, 2, 1]
- [1, 2, 3, 4]
- [3, 2, 1, 4]
Check your answer
[4, 3, 2, 1]. reversed() returns a view over the original list, so the later add(4) shows up — at the front of the reversed view.
What does this print?
var m = new LinkedHashMap<String, Integer>();
m.put("a", 1);
m.put("b", 2);
m.putFirst("z", 0);
System.out.println(m.firstEntry());
System.out.println(m.lastEntry());- a=1 b=2
- z=0 b=2
- z=0 a=1
- a=1 z=0
Check your answer
z=0 b=2. putFirst (Java 21) inserts the mapping at the front of the LinkedHashMap's encounter order. So the first entry is z=0 and the last is still b=2.