Sequenced Collections: A Deep Dive into JDK 21's addition to Collections Framework

    Sequenced Collections: A Deep Dive into JDK 21's addition to Collections Framework

    27/04/2025

    Introduction

    Sequenced collections introduced in Java 21 provide a unified way to work with ordered collections and make your code cleaner and more expressive while working with ordered collections. Some Collections in Java Collections framework has a defined encounter order which means they maintain the order of elements. For example, List, Deque and LinkedHashset are ordered collections, but they all didn't have common semantics or a common super interface for this ordering till Java 21. Sequenced collections in Java 21 fill this gap by introducing three new interfaces: SequencedCollection, SequencedSet, and SequencedMap. These interfaces provide a unified way to work with ordered collections, making it easier to write generic code that can handle any ordered collection. Also this introduces new methods to work with first and last elements, and a reversed() method to get a reversed view of the collection.

    Java Collections Hierarchy with Sequenced Collections (JDK 21) Existing Interface Implementation Class New in JDK 21 Iterable Collection Set Unique Elements SortedSet NavigableSet Sorted Order TreeSet SequencedCollection Ordered List Allows Duplicates ArrayList LinkedList SequencedSet Unique Elements LinkedHashSet Insertion Order Queue Deque Double-Ended Queue ArrayDeque Map SequencedMap SortedMap LinkedHashMap NavigableMap TreeMap

    Changes added in Sequenced Collections

    Now let's explore the new interfaces and methods introduced in JDK 21's Sequenced Collections API.

    1. SequencedCollection

    This interface extends Collection and adds methods for working with elements at both ends of an ordered collection:

    public interface SequencedCollection<E> extends Collection<E> { // Adds element at the end default boolean addLast(E e); // Adds element at the beginning boolean addFirst(E e); // Gets the first element E getFirst(); // Gets the last element E getLast(); // Removes and returns the first element E removeFirst(); // Removes and returns the last element E removeLast(); // Returns a reversed view of this collection SequencedCollection<E> reversed(); }

    2. SequencedSet

    This interface extends both Set and SequencedCollection, representing an ordered collection that requires unique elements:

    public interface SequencedSet<E> extends Set<E>, SequencedCollection<E> { // Override to return a SequencedSet @Override SequencedSet<E> reversed(); }

    3. SequencedMap

    This interface extends Map and adds methods for working with the first and last entries:

    public interface SequencedMap<K, V> extends Map<K, V> { // Map entry operations Map.Entry<K, V> firstEntry(); Map.Entry<K, V> lastEntry(); Map.Entry<K, V> pollFirstEntry(); Map.Entry<K, V> pollLastEntry(); // Add entries at either end default V putFirst(K k, V v); default V putLast(K k, V v); // Returns a reversed view SequencedMap<K, V> reversed(); // Returns sequenced views of the map SequencedSet<K> sequencedKeySet(); Collection<V> sequencedValues(); SequencedSet<Entry<K, V>> sequencedEntrySet(); }

    Practical Examples

    Now, let's explore some practical examples of how these new interfaces can make your code cleaner and more expressive.

    Example 1: Working with the First and Last Elements

    Before JDK 21, getting the first or last element of a List required using indexed access:

    // Before JDK 21 List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie", "David")); String first = names.get(0); // Get first element String last = names.get(names.size() - 1); // Get last element // Remove the first element names.remove(0); // Remove the last element names.remove(names.size() - 1);

    With JDK 21's Sequenced Collections:

    // With JDK 21 List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie", "David")); String first = names.getFirst(); // Cleaner syntax String last = names.getLast(); // No need to calculate index names.removeFirst(); // Simpler names.removeLast(); // More intuitive

    All these 4 methods getFirst(), getLast(), removeFirst(), and removeLast() are now available for all ordered collections, including List, Deque, LinkedHashSet, NavigableSet etc. since they all implement SequencedCollection.

    Example 2: Iterating in Reverse Order

    Before JDK 21, iterating a collection in reverse order varied by collection type:

    // For List - using ListIterator List<String> list = Arrays.asList("A", "B", "C", "D"); ListIterator<String> iterator = list.listIterator(list.size()); while (iterator.hasPrevious()) { System.out.println(iterator.previous()); } // For Deque - using descendingIterator Deque<String> deque = new ArrayDeque<>(Arrays.asList("A", "B", "C", "D")); Iterator<String> descIterator = deque.descendingIterator(); while (descIterator.hasNext()) { System.out.println(descIterator.next()); } // For LinkedHashSet - not straightforward, usually required copying to another collection LinkedHashSet<String> set = new LinkedHashSet<>(Arrays.asList("A", "B", "C", "D")); List<String> reversedList = new ArrayList<>(set); Collections.reverse(reversedList); for (String item : reversedList) { System.out.println(item); }

    With JDK 21, you can use the uniform reversed() method:

    // Works for any SequencedCollection List<String> list = Arrays.asList("A", "B", "C", "D"); for (String s : list.reversed()) { System.out.println(s); // Prints D, C, B, A } Deque<String> deque = new ArrayDeque<>(Arrays.asList("A", "B", "C", "D")); for (String s : deque.reversed()) { System.out.println(s); // Prints D, C, B, A } LinkedHashSet<String> set = new LinkedHashSet<>(Arrays.asList("A", "B", "C", "D")); for (String s : set.reversed()) { System.out.println(s); // Prints D, C, B, A }

    Example 3: Working with LinkedHashSet

    Before JDK 21, reordering elements in a LinkedHashSet was difficult:

    // Before JDK 21 LinkedHashSet<String> set = new LinkedHashSet<>(); set.add("A"); set.add("B"); set.add("C"); // Output: [A, B, C] // To move B to the end - requires removing and re-adding set.remove("B"); set.add("B"); // Output: [A, C, B] // Getting first/last elements was complicated Iterator<String> iterator = set.iterator(); String first = iterator.hasNext() ? iterator.next() : null; // Getting last element was even worse - had to iterate through everything String last = null; for (String s : set) { last = s; }

    With JDK 21:

    // With JDK 21 LinkedHashSet<String> set = new LinkedHashSet<>(); set.add("A"); set.add("B"); set.add("C"); // Output: [A, B, C] // Move B to the end set.addLast("B"); // Automatically removes B from its original position and adds it at the end // Output: [A, C, B] // Getting first/last elements is straightforward String first = set.getFirst(); // Returns "A" String last = set.getLast(); // Returns "B"

    Example 4: Working with Sequenced Maps

    Before JDK 21, working with the first or last entries of an ordered map was cumbersome:

    // Before JDK 21 LinkedHashMap<Integer, String> map = new LinkedHashMap<>(); map.put(1, "One"); map.put(2, "Two"); map.put(3, "Three"); // Getting first entry Map.Entry<Integer, String> firstEntry = null; for (Map.Entry<Integer, String> entry : map.entrySet()) { firstEntry = entry; break; } // Getting last entry Map.Entry<Integer, String> lastEntry = null; for (Map.Entry<Integer, String> entry : map.entrySet()) { lastEntry = entry; }

    With JDK 21:

    // With JDK 21 LinkedHashMap<Integer, String> map = new LinkedHashMap<>(); map.put(1, "One"); map.put(2, "Two"); map.put(3, "Three"); // Getting first and last entries is simple Map.Entry<Integer, String> firstEntry = map.firstEntry(); // Entry(1, "One") Map.Entry<Integer, String> lastEntry = map.lastEntry(); // Entry(3, "Three") // Putting an entry at the beginning or end map.putFirst(0, "Zero"); // Now: {0=Zero, 1=One, 2=Two, 3=Three} map.putLast(4, "Four"); // Now: {0=Zero, 1=One, 2=Two, 3=Three, 4=Four} // If key already exists, it gets moved to the requested position map.putFirst(3, "Three"); // Moves key 3 to the beginning // Now: {3=Three, 0=Zero, 1=One, 2=Two, 4=Four}

    Example 5: Working with Reversed Views

    The reversed() method returns a view of the original collection with elements in reverse order. Any changes to the view affect the underlying collection, and vice versa:

    List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C")); List<String> reversed = list.reversed(); System.out.println(list); // [A, B, C] System.out.println(reversed); // [C, B, A] // Modifications to the reversed view affect the original list reversed.set(0, "Z"); // Modifies the last element of original list System.out.println(list); // [A, B, Z] System.out.println(reversed); // [Z, B, A] // Adding to the original affects the reversed view list.add("D"); System.out.println(list); // [A, B, Z, D] System.out.println(reversed); // [D, Z, B, A] // Removing from the reversed view affects the original reversed.remove(0); // Removes the last element from the original System.out.println(list); // [A, B, Z] System.out.println(reversed); // [Z, B, A]

    Example 6: Creating a Reversed Stream

    Before JDK 21, streaming a list in reverse order required somewhat complex code:

    // Before JDK 21 List<String> list = Arrays.asList("A", "B", "C"); // Creating a reverse-ordered stream (verbose) Stream<String> reversedStream = StreamSupport.stream( Spliterators.spliteratorUnknownSize( new Iterator<String>() { private final ListIterator<String> listIter = list.listIterator(list.size()); public boolean hasNext() { return listIter.hasPrevious(); } public String next() { return listIter.previous(); } }, Spliterator.ORDERED ), false );

    With JDK 21:

    // With JDK 21 List<String> list = Arrays.asList("A", "B", "C"); // Creating a reverse-ordered stream (clean) Stream<String> reversedStream = list.reversed().stream();

    When to Use Sequenced Collections

    The Sequenced Collections API is particularly useful when:

      1. Writing generic algorithms that need to work with any ordered collection, regardless of whether it's a List, Deque, LinkedHashSet, etc.
      1. Creating APIs that need to accept or return ordered collections without forcing a specific implementation.
      1. Working with the endpoints of collections, such as accessing the first or last element.
      1. Iterating in reverse without having to remember different approaches for different collection types.

    Conclusion

    The Sequenced Collections API provides a consistent and powerful way to work with ordered collections in Java.

    To stay updated with the latest updates in Java and Spring, follow us on YouTube, LinkedIn, and Medium.

    References

    Summarise

    Transform Your Learning

    Get instant AI-powered summaries of YouTube videos and websites. Save time while enhancing your learning experience.

    Instant video summaries
    Smart insights extraction
    Channel tracking