Java 21 Sequenced Collections Explained

Introduction Many Java collections have a defined order - a List, a Deque, a LinkedHashSet, a TreeSet, a LinkedHashMap. Yet before Java 21 they had no common way to get the first or last element, or to process the elements in reverse. Getting the last element of a List meant list.get(list.size() - 1); of a LinkedHashSet, iterating through all of it. Java 21Click to read tutorial: Overview of New Features in Java 21 (JEP 431) added sequenced collections: three new interfaces, SequencedCollection, SequencedSet and SequencedMap, which give every ordered collection the same methods for its first and last elements and a reversed view. This tutorial explains them with examples. The new interfaces
  • SequencedCollection<E> - a collection with a defined encounter order. Methods: getFirst(), getLast(), addFirst(e), addLast(e), removeFirst(), removeLast() and reversed(). List and Deque now extend it.
  • SequencedSet<E> - a sequenced collection with no duplicates; reversed() returns a SequencedSet. LinkedHashSet implements it, and SortedSet extends it.
  • SequencedMap<K,V> - a map with a defined order of entries. Methods: firstEntry(), lastEntry(), pollFirstEntry(), pollLastEntry(), putFirst(k, v), putLast(k, v), reversed(), and sequenced views sequencedKeySet(), sequencedValues() and sequencedEntrySet(). LinkedHashMap implements it, and SortedMap extends it.
Because the interfaces were inserted into the existing hierarchy, every existing ordered collection gained the methods, with no change to your code.
First, last and reversed on lists and sets
getFirst, getLast and reversed on different collections
//SequencedCollectionsDemo.java
import java.util.*;

public class SequencedCollectionsDemo {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>(List.of("Tom", "Harry", "Sam"));
    System.out.println("List first/last : " + list.getFirst() + " / " + list.getLast());   // was get(size()-1)
    list.addFirst("Ann");                                  // was add(0, "Ann")
    System.out.println("After addFirst  : " + list);
    System.out.println("Reversed view   : " + list.reversed());

    LinkedHashSet<String> set = new LinkedHashSet<>(List.of("red", "green", "blue"));
    System.out.println("Set first/last  : " + set.getFirst() + " / " + set.getLast());    // was impossible without iterating
    set.addFirst("blue");                                  // already present: moved to the front
    System.out.println("After addFirst  : " + set);

    TreeSet<Integer> sorted = new TreeSet<>(List.of(30, 10, 20));
    System.out.println("TreeSet reversed: " + sorted.reversed());

    Deque<Integer> deque = new ArrayDeque<>(List.of(1, 2, 3));
    System.out.println("Deque last      : " + deque.getLast() + ", reversed " + deque.reversed());
  }
}
 OUTPUT of the above code
List first/last : Tom / Sam
After addFirst  : [Ann, Tom, Harry, Sam]
Reversed view   : [Sam, Harry, Tom, Ann]
Set first/last  : red / blue
After addFirst  : [blue, red, green]
TreeSet reversed: [30, 20, 10]
Deque last      : 3, reversed [3, 2, 1]
Explanation of the code
  • getFirst() and getLast() work the same way on a List, a LinkedHashSet, a TreeSet and a Deque. On an empty collection they throw NoSuchElementException.
  • On a LinkedHashSet, addFirst() of an element which is already present moves it to the front - so a LinkedHashSet can now be used as an ordered set whose order you control.
  • reversed() returns a view, not a copy: it costs nothing to create, and changes to the original show through it - and vice versa for modifiable collections.
  • On a TreeSet, whose order is determined by its comparator, addFirst() and addLast() throw UnsupportedOperationException, since the set decides the position itself. Unmodifiable collections, such as those from List.of(), throw it for all the modifying methods.
Sequenced maps
SequencedMap methods on a LinkedHashMap
//SequencedMapDemo.java
import java.util.LinkedHashMap;
import java.util.SequencedMap;

public class SequencedMapDemo {
  public static void main(String[] args) {
    SequencedMap<String, Integer> scores = new LinkedHashMap<>();
    scores.put("Tom", 78);
    scores.put("Harry", 91);
    scores.put("Sam", 64);

    System.out.println("First entry : " + scores.firstEntry());
    System.out.println("Last entry  : " + scores.lastEntry());
    scores.putFirst("Ann", 88);                        // insert (or move) at the front
    System.out.println("After putFirst: " + scores);
    System.out.println("Reversed keys : " + scores.reversed().sequencedKeySet());

    var oldest = scores.pollFirstEntry();              // remove and return the first entry
    System.out.println("Polled " + oldest + ", left " + scores);
  }
}
 OUTPUT of the above code
First entry : Tom=78
Last entry  : Sam=64
After putFirst: {Ann=88, Tom=78, Harry=91, Sam=64}
Reversed keys : [Sam, Harry, Tom, Ann]
Polled Ann=88, left {Tom=78, Harry=91, Sam=64}
pollFirstEntry() and putFirst() make a LinkedHashMap convenient as a simple ordered cache or queue of keyed entries - operations which previously needed workarounds.
Writing methods against the new interfaces The new interfaces are also useful as parameter types, for methods which need an ordered collection but do not care which kind -
A method accepting any sequenced collection
//SequencedParameterDemo.java
import java.util.*;

public class SequencedParameterDemo {
  static <E> String ends(SequencedCollection<E> items) {
    return items.isEmpty() ? "(empty)" : items.getFirst() + " ... " + items.getLast();
  }

  public static void main(String[] args) {
    System.out.println(ends(List.of(1, 2, 3)));
    System.out.println(ends(new ArrayDeque<>(List.of("a", "b"))));
    System.out.println(ends(new TreeSet<>(Set.of(5, 1, 9))));
    System.out.println(ends(Collections.unmodifiableSequencedCollection(new LinkedHashSet<>(List.of("x")))));
  }
}
 OUTPUT of the above code
1 ... 3
a ... b
1 ... 9
x ... x
Collections also got unmodifiableSequencedCollection(), unmodifiableSequencedSet() and unmodifiableSequencedMap(). A HashSet or HashMap, which has no defined order, is deliberately not sequenced, so passing one to ends() is a compile error. Summary In this tutorial we saw how Java 21's sequenced collections give every ordered collection a common set of methods. We used getFirst(), getLast(), addFirst() and the reversed() view on lists, sets and deques, and firstEntry(), putFirst() and pollFirstEntry() on a LinkedHashMap. Lastly, we wrote a method which accepts any SequencedCollection. Next, read about generational ZGCClick to read tutorial: Java 21 Generational ZGC Explained.