Career Guide

Mastering Sequenced Collections in Java 21 (The Missing Collections Interfaces)

For decades, Java collections lacked a unified way to access first/last elements and traverse in reverse. JDK 21 fixed this. Here is how to use SequencedCollection, SequencedSet, and SequencedMap.

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: Iterator navigation 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:

  1. SequencedCollection: Extends Collection. Represents a collection with a well-defined first and last element.
  2. SequencedSet: Extends SequencedCollection and Set. Ensures uniqueness while maintaining order.
  3. 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.

📢 Share this article