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.
Now let's explore the new interfaces and methods introduced in JDK 21's Sequenced Collections API.
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(); }
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(); }
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(); }
Now, let's explore some practical examples of how these new interfaces can make your code cleaner and more expressive.
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
.
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 }
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"
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}
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]
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();
The Sequenced Collections API is particularly useful when:
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.
Explore the most significant changes in JDK 24. Learn about language enhancements, performance improvements, new APIs, and security upgrades with practical code examples.
Explore the top 5 features released from Java 21 to Java 23, including Virtual Threads, Pattern Matching with Records and Sealed Classes, Structured Concurrency, Scoped Values, and Stream Gatherers. Learn how these features can enhance your Java applications.
Get instant AI-powered summaries of YouTube videos and websites. Save time while enhancing your learning experience.