Sequenced Collections are introduced in java 21.
sequenced collection is a Collection which allows common operations at both ends.
Common operations implies addition, removal, traversal of elements.
This article will explain Java 21 Sequenced Collections in detail.

Concept of Encounter Order

The concept of encounter order in collections refers to the specific order in which the elements are traversed or accessed in a particular collection type.
This order is vital for maintaining predictability and consistency in how elements are handled, especially when iterating through the collection.
Encounter order plays a crucial role in determining how elements are accessed and manipulated within a collection.
Because Java’s collections framework does not have a standardized approach to managing order, codebases have become disorganized and it has become more difficult to clearly express important concepts through APIs.

Suppose you want to get the first and last element of a list, you would use below code

// first list element
list.get(0);

// last list element
list.get(list.size() - 1);

To get the first and last elements of a Deque or SortedSet, you would use

// first element
deque.getFirst();
sortedSet.getFirst();

// last element
deque.getLast();
sortedSet.getLast();

Further, if you are using LinkedHashSet, you need to use iterator() to get the first and subsequent elements and there is NO direct way to get the last element.

You can see the inconsistency between different collection types prior to java 21 and this is the motivation behind Sequenced Collections.

What are Sequenced Collections

Sequenced Collections are a set of new interfaces added in java collection framework, that provide a uniform approach to access the first and last elements of a collection.
Also, these interfaces allow traversing a collection in reverse order.

The new interfaces added as Sequenced Collections are:

  1. SequencedCollection
  2. SequencedSet
  3. SequencedMap

With the addition of these new interfaces, the new Collection hierarchy becomes

1. SequencedCollection

SequencedCollection is a new interface that extends Collection.
It provides methods to add, remove and retrieve elements from both ends of a collection with addFirst(), addLast(), removeFirst(), removeLast(), getFirst() and getLast() methods respectively.
Besides, you can also get a reversed view of the collection with its reversed() method.

All get() methods of SequencedCollection are default methods while reversed() is abstract.
Also, add() and remove() methods throw UnsupportedOperationException, since some of the collections might be unmodifiable.

SequencedCollection interface is extends by List interface and so, now you can simply get first and last elements from an ArrayList using getFirst() and getLast() methods as shown below

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
String first = list.getFirst(); // A
String last = list.getLast();   // C

Now, you can easily reverse a list with reversed() method.
Note that any changes done to the list returned by reversed() will also reflect in the original list.

List<String> reversed = list.reversed();
reversed.add("X");
System.out.println(list);  // [X, A, B, C]

2. SequencedSet

SequencedSet interface extends SequencedCollection.
As such, it contains all the methods of SequencedCollection, that define the encounter order of its elements.
A SequencedSet overrides reversed() method of SequencedCollection. Below is its source code from Java API

public interface SequencedSet<E> extends SequencedCollection<E>, Set<E> {

    SequencedSet<E> reversed();

}

Difference between its reversed() method and its parent is the return type.
reversed() from SequencedCollection returns a value of type SequencedCollection.

3. SequencedMap

Another new interface added in java 21 is SequencedMap, which is specific to support operations at both ends for java map.
SequencedMap does not extend SequencedCollection, rather extends Map interface.

Methods defined in SequencedMap apply to map entries(key-value pairs) instead of individual elements as shown below

public interface SequencedMap<K, V> extends Map<K, V> {
  // get first entry
  Map.Entry<K,V> firstEntry();
  
  // get last entry
  Map.Entry<K,V> lastEntry();
  
  // poll first entry
  Map.Entry<K,V> pollFirstEntry();

  // poll last entry
  Map.Entry<K,V> pollLastEntry();
}

SequencedMap interface also defines the reversed() method, which provides a reverse-ordered view of this map.

Remember that SequencedMap is implemented by LinkedHashMap, so its methods can not be leveraged if you are used a HashMap.

Example of SequencedMap is given below

LinkedHashMap<Integer, String> map = new LinkedHashMap<>();
map.put(1, "A");
map.put(2, "B");
// get first entry
Entry<Integer,String> firstEntry = map.firstEntry();
System.out.println(firstEntry);  // 1=A

// get last entry
Entry<Integer,String> lastEntry = map.lastEntry();
System.out.println(lastEntry);  // 2=B
System.out.println(map);  {1=A, 2=B}

// remove first entry
firstEntry = map.pollFirstEntry();
System.out.println(firstEntry);  // 1=A
System.out.println(map);  // {2=B}

NoSuchElementException

All the get*() and remove*() methods of SequencedCollection such as getFirst(), getLast(), removeFirst() and removeLast() will throw NoSuchElementException if the collection is empty or does not contain any elements.

This is because these methods use an iterator over the collection and directly call its next() method without checking if the collection has any elements as below

default E getFirst() {
  return this.iterator().next();
}

default E getLast() {
  return this.reversed().iterator().next();
}

Iterator’s next() method throws NoSuchElementException when there is no next element.

UnsupportedOperationException

Methods that perform manipulation to a collection such as addFirst(), addLast(), removeFirst() and removeLast() have been implemented as default methods in SequencedCollection.

These methods throw UnsupportedOperationException in this default implementation.
This has been done to support collections that are unmodifiable. Example,

List<String> list = List.of("A","B");
list.addFirst("C");

This will result in

Exception in thread “main” java.lang.UnsupportedOperationException

at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)

at java.base/java.util.ImmutableCollections$AbstractImmutableList.add(ImmutableCollections.java:258)

at java.base/java.util.List.addFirst(List.java:796)

Above code results in an error since List.of() returns an object which is immutable and you cannot add or remove objects from it.

Conclusion

Java 21 has provided a consistent approach to access collection elements from both ends across various collection classes.

With 3 new interfaces, you can now access first and last elements of collections in a uniform manner.

These new interfaces are SequencedCollection, SequencedSet and SequencedMap that we discussed in this article.