For a language that has been around for three decades, Java’s collections framework is remarkably robust. However, it had one glaring omission: there was no unified interface to represent collections that have a defined encounter order. If you wanted to get the first or last element of a collection, the code you wrote depended entirely on the concrete implementation you were using. In JDK 21, Java resolved this historical inconsistency by introducing Sequenced Collections.
The Historical Problem
To understand why Sequenced Collections are a major quality-of-life improvement, look at how developers had to retrieve the last element of different collection types prior to Java 21:
- ArrayList:
list.get(list.size() - 1) - LinkedHashSet:
Iteratornavigation or copying the set to a list. - TreeSet:
set.last() - Deque:
deque.getLast()
Accessing the last element of a list was verbosely mathematical, while doing the same for a sorted set required checking specific subclass methods. If you wrote a method that accepted a generic Collection and needed to access the last element, you had to write custom type checks and casts. This lack of a common interface made API design clunky.
The New Interface Hierarchy
JDK 21 introduced three new interfaces into the java.util package:
- SequencedCollection: Extends
Collection. Represents a collection with a well-defined first and last element. - SequencedSet: Extends
SequencedCollectionandSet. Ensures uniqueness while maintaining order. - SequencedMap: Extends
Map. Represents a map with ordered keys.
These interfaces are now integrated into the existing hierarchy. For example, List extends SequencedCollection, LinkedHashSet implements SequencedSet, and LinkedHashMap implements SequencedMap.
Accessing and Modifying Ordered Elements
The SequencedCollection interface provides a clean, standardized set of methods to access and modify elements at both ends:
- Getters:
getFirst(),getLast() - Adders:
addFirst(E),addLast(E) - Removers:
removeFirst(),removeLast()
If the collection is empty, the getter and remover methods throw a NoSuchElementException. These methods bring consistency across lists, sets, and double-ended queues.
The Power of Reversed Views
Perhaps the most powerful method introduced is reversed(). This method returns a reverse-ordered view of the original collection.
It is important to note that reversed() does not copy the collection; it returns a live, O(1) time-complexity view. Any changes made to the original collection are reflected in the reversed view, and vice versa.
Here is a code example demonstrating how to use SequencedCollection and SequencedSet operations:
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.SequencedCollection;
import java.util.SequencedSet;
public class SequenceDemo {
public static void main(String[] args) {
// List implements SequencedCollection
SequencedCollection<String> list = new ArrayList<>();
list.add("Middle");
list.addFirst("Start");
list.addLast("End");
System.out.println("First: " + list.getFirst()); // Output: Start
System.out.println("Last: " + list.getLast()); // Output: End
// Reverse view is O(1)
SequencedCollection<String> reversedList = list.reversed();
System.out.println("Reversed: " + reversedList); // Output: [End, Middle, Start]
// LinkedHashSet implements SequencedSet
SequencedSet<Integer> set = new LinkedHashSet<>();
set.add(10);
set.addFirst(5);
set.addLast(15);
System.out.println("Set First: " + set.getFirst()); // Output: 5
System.out.println("Set Last: " + set.getLast()); // Output: 15
}
}
A Deeper Dive into the Reversed View Behavior
The reversed() method does not perform any copying or sorting under the hood. It returns a wrapper view of the collection that accesses the original elements in reverse order. This operation runs in O(1) constant time complexity and consumes minimal memory.
However, since this view is "live," it is read-and-write. If you modify the reversed view, you modify the original collection. For instance, if you call addFirst() on the reversed view, the element is actually added to the end of the original collection!
Let's see this in action:
SequencedCollection<String> list = new ArrayList<>();
list.add("First");
list.add("Second");
SequencedCollection<String> rev = list.reversed();
rev.addFirst("Third"); // Adding to the start of reversed is adding to the end of list!
System.out.println(list); // Prints: [First, Second, Third]
This bidirectional behavior is incredibly convenient but requires careful handling. Developers must be aware of this side effect when passing reversed views to other methods that might mutate the collection.
Working with SequencedMap
Similarly, SequencedMap provides ordered access to keys, values, and entry sets. It includes methods like firstEntry(), lastEntry(), pollFirstEntry(), pollLastEntry(), putFirst(), putLast(), and a sequencedKeySet().
Here is how you can use SequencedMap:
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.SequencedMap;
public class MapDemo {
public static void main(String[] args) {
SequencedMap<String, String> map = new LinkedHashMap<>();
map.put("Key2", "Value2");
map.putFirst("Key1", "Value1");
map.putLast("Key3", "Value3");
System.out.println("First Entry: " + map.firstEntry()); // Key1=Value1
System.out.println("Last Entry: " + map.lastEntry()); // Key3=Value3
// Print keys in reverse order
for (String key : map.sequencedKeySet().reversed()) {
System.out.println(key + " -> " + map.get(key));
}
}
}
With Sequenced Collections, Java developers no longer need to write boilerplate code or use third-party libraries simply to perform basic ordered collections operations. By upgrading to JDK 21, you immediately gain access to a cleaner, more intuitive API that makes working with collections a breeze.