google-site-verification: google61fe8ba583a51912.html
Sequenced Collections in Java 21: SequencedSet and SequencedMap

Java 21 introduced Sequenced Collections as a preview feature (JEP 431) that provides a consistent API for collections with defined encounter orders. These new interfaces—SequencedSet, SequencedMap, and SequencedCollection—offer standardized methods for accessing first and last elements and reversing views.


Overview of Sequenced Collections

The Problem Before Java 21

Before Java 21, different collection types had inconsistent APIs for accessing endpoints:

  • List had getFirst(), getLast()
  • Deque had getFirst(), getLast(), getFirst(), getLast()
  • SortedSet had first(), last()
  • LinkedHashSet had no direct endpoint access

The Solution: Sequenced Collections

Java 21 introduces a unified hierarchy:

Collection └── SequencedCollection ├── List ├── Deque └── SequencedSet ├── LinkedHashSet └── SortedSet/NavigableSet Map └── SequencedMap ├── LinkedHashMap └── SortedMap/NavigableMap

SequencedSet Interface

Definition and Methods

interface SequencedSet<E> extends SequencedCollection<E>, Set<E> { // Inherits from SequencedCollection: // E getFirst(); // E getLast(); // void addFirst(E); // void addLast(E); // E removeFirst(); // E removeLast(); // SequencedSet-specific: SequencedSet<E> reversed(); }

Example 1: Basic SequencedSet Operations

import java.util.*; public class SequencedSetDemo { public static void main(String[] args) { // Create a SequencedSet (LinkedHashSet implements SequencedSet) SequencedSet<String> fruitSet = new LinkedHashSet<>(); // Add elements - maintains insertion order fruitSet.add("Apple"); fruitSet.add("Banana"); fruitSet.add("Cherry"); fruitSet.add("Date"); System.out.println("Original set: " + fruitSet); // Access first and last elements System.out.println("First element: " + fruitSet.getFirst()); System.out.println("Last element: " + fruitSet.getLast()); // Add elements at ends fruitSet.addFirst("Apricot"); // Adds to beginning fruitSet.addLast("Elderberry"); // Adds to end System.out.println("After adding first/last: " + fruitSet); // Remove elements from ends String removedFirst = fruitSet.removeFirst(); String removedLast = fruitSet.removeLast(); System.out.println("Removed first: " + removedFirst); System.out.println("Removed last: " + removedLast); System.out.println("After removals: " + fruitSet); // Get reversed view SequencedSet<String> reversedSet = fruitSet.reversed(); System.out.println("Reversed view: " + reversedSet); // Modifications through reversed view affect original reversedSet.addFirst("Zucchini"); System.out.println("After adding to reversed: " + fruitSet); } }

Output:

Original set: [Apple, Banana, Cherry, Date] First element: Apple Last element: Date After adding first/last: [Apricot, Apple, Banana, Cherry, Date, Elderberry] Removed first: Apricot Removed last: Elderberry After removals: [Apple, Banana, Cherry, Date] Reversed view: [Date, Cherry, Banana, Apple] After adding to reversed: [Zucchini, Apple, Banana, Cherry, Date]

Example 2: SequencedSet with SortedSet

import java.util.*; public class SortedSetSequencedDemo { public static void main(String[] args) { // TreeSet also implements SequencedSet SequencedSet<Integer> numbers = new TreeSet<>(); numbers.add(30); numbers.add(10); numbers.add(20); numbers.add(40); System.out.println("Sorted set: " + numbers); System.out.println("First: " + numbers.getFirst()); // 10 System.out.println("Last: " + numbers.getLast()); // 40 // reversed() returns a view in descending order SequencedSet<Integer> descending = numbers.reversed(); System.out.println("Descending view: " + descending); // Attempting mutating operations on sorted sets try { numbers.addFirst(5); // Would break sorting - throws UnsupportedOperationException } catch (UnsupportedOperationException e) { System.out.println("addFirst not supported by TreeSet"); } try { numbers.addLast(50); // Would break sorting - throws UnsupportedOperationException } catch (UnsupportedOperationException e) { System.out.println("addLast not supported by TreeSet"); } } }

Output:

Sorted set: [10, 20, 30, 40] First: 10 Last: 40 Descending view: [40, 30, 20, 10] addFirst not supported by TreeSet addLast not supported by TreeSet

SequencedMap Interface

Definition and Methods

interface SequencedMap<K, V> extends Map<K, V> { // Entry accessors Entry<K, V> firstEntry(); Entry<K, V> lastEntry(); // Key accessors K firstKey(); K lastKey(); // Element addition Entry<K, V> pollFirstEntry(); Entry<K, V> pollLastEntry(); // Element removal Entry<K, V> putFirst(K key, V value); Entry<K, V> putLast(K key, V value); // Reversed view SequencedMap<K, V> reversed(); // Sequenced views SequencedSet<K> sequencedKeySet(); SequencedCollection<V> sequencedValues(); SequencedSet<Entry<K, V>> sequencedEntrySet(); }

Example 3: Basic SequencedMap Operations

import java.util.*; public class SequencedMapDemo { public static void main(String[] args) { // LinkedHashMap implements SequencedMap SequencedMap<String, Integer> studentScores = new LinkedHashMap<>(); // Add elements in order studentScores.put("Alice", 85); studentScores.put("Bob", 92); studentScores.put("Charlie", 78); studentScores.put("Diana", 95); System.out.println("Original map: " + studentScores); // Access first and last entries System.out.println("First entry: " + studentScores.firstEntry()); System.out.println("Last entry: " + studentScores.lastEntry()); System.out.println("First key: " + studentScores.firstKey()); System.out.println("Last key: " + studentScores.lastKey()); // Add elements at ends studentScores.putFirst("Aaron", 88); // Adds to beginning studentScores.putLast("Zoe", 91); // Adds to end System.out.println("After putFirst/putLast: " + studentScores); // Poll (remove and return) entries from ends Map.Entry<String, Integer> polledFirst = studentScores.pollFirstEntry(); Map.Entry<String, Integer> polledLast = studentScores.pollLastEntry(); System.out.println("Polled first: " + polledFirst); System.out.println("Polled last: " + polledLast); System.out.println("After polling: " + studentScores); // Get reversed view SequencedMap<String, Integer> reversedMap = studentScores.reversed(); System.out.println("Reversed map: " + reversedMap); // Sequenced views System.out.println("Sequenced keys: " + studentScores.sequencedKeySet()); System.out.println("Sequenced values: " + studentScores.sequencedValues()); System.out.println("Sequenced entries: " + studentScores.sequencedEntrySet()); } }

Output:

Original map: {Alice=85, Bob=92, Charlie=78, Diana=95} First entry: Alice=85 Last entry: Diana=95 First key: Alice Last key: Diana After putFirst/putLast: {Aaron=88, Alice=85, Bob=92, Charlie=78, Diana=95, Zoe=91} Polled first: Aaron=88 Polled last: Zoe=91 After polling: {Alice=85, Bob=92, Charlie=78, Diana=95} Reversed map: {Diana=95, Charlie=78, Bob=92, Alice=85} Sequenced keys: [Alice, Bob, Charlie, Diana] Sequenced values: [85, 92, 78, 95] Sequenced entries: [Alice=85, Bob=92, Charlie=78, Diana=95]

Example 4: SequencedMap with SortedMap

import java.util.*; public class SortedMapSequencedDemo { public static void main(String[] args) { // TreeMap implements SequencedMap SequencedMap<Integer, String> products = new TreeMap<>(); products.put(103, "Keyboard"); products.put(101, "Mouse"); products.put(104, "Monitor"); products.put(102, "Headphones"); System.out.println("Sorted map: " + products); System.out.println("First entry: " + products.firstEntry()); System.out.println("Last entry: " + products.lastEntry()); // Reversed view (descending order) SequencedMap<Integer, String> descendingProducts = products.reversed(); System.out.println("Descending order: " + descendingProducts); // Poll operations work with sorted maps Map.Entry<Integer, String> firstProduct = products.pollFirstEntry(); Map.Entry<Integer, String> lastProduct = products.pollLastEntry(); System.out.println("Removed first: " + firstProduct); System.out.println("Removed last: " + lastProduct); System.out.println("After polling: " + products); // putFirst/putLast are not supported by TreeMap try { products.putFirst(100, "Webcam"); } catch (UnsupportedOperationException e) { System.out.println("putFirst not supported by TreeMap"); } } }

Output:

Sorted map: {101=Mouse, 102=Headphones, 103=Keyboard, 104=Monitor} First entry: 101=Mouse Last entry: 104=Monitor Descending order: {104=Monitor, 103=Keyboard, 102=Headphones, 101=Mouse} Removed first: 101=Mouse Removed last: 104=Monitor After polling: {102=Headphones, 103=Keyboard} putFirst not supported by TreeMap

Practical Use Cases

Example 5: LRU (Least Recently Used) Cache

import java.util.*; public class LRUCache<K, V> { private final SequencedMap<K, V> cache; private final int maxSize; public LRUCache(int maxSize) { this.maxSize = maxSize; this.cache = new LinkedHashMap<>(maxSize, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > maxSize; } }; } public V get(K key) { return cache.get(key); } public void put(K key, V value) { cache.put(key, value); } public V getRecent() { return cache.isEmpty() ? null : cache.getLast(); } public V getOldest() { return cache.isEmpty() ? null : cache.getFirst(); } public void displayCache() { System.out.println("Cache (oldest to newest): " + cache); System.out.println("Oldest: " + getOldest()); System.out.println("Newest: " + getRecent()); } public static void main(String[] args) { LRUCache<String, String> cache = new LRUCache<>(3); cache.put("A", "Data A"); cache.put("B", "Data B"); cache.put("C", "Data C"); cache.displayCache(); // Access pattern affects order cache.get("A"); // Moves A to most recent cache.displayCache(); // Adding new element evicts oldest cache.put("D", "Data D"); cache.displayCache(); } }

Output:

Cache (oldest to newest): {A=Data A, B=Data B, C=Data C} Oldest: Data A Newest: Data C Cache (oldest to newest): {B=Data B, C=Data C, A=Data A} Oldest: Data B Newest: Data A Cache (oldest to newest): {C=Data C, A=Data A, D=Data D} Oldest: Data C Newest: Data D

Example 6: Navigation History

import java.util.*; public class NavigationHistory { private final SequencedDeque<String> history = new LinkedHashSet<>(); private final int maxHistorySize; public NavigationHistory(int maxHistorySize) { this.maxHistorySize = maxHistorySize; } public void visitPage(String url) { history.remove(url); // Remove if already exists (avoid duplicates) history.addLast(url); // Add as most recent // Trim history if exceeds max size while (history.size() > maxHistorySize) { history.removeFirst(); } } public String goBack() { if (history.size() < 2) { return null; } // Remove current history.removeLast(); // Return previous return history.getLast(); } public List<String> getHistory() { return new ArrayList<>(history); } public List<String> getRecentFirst() { List<String> recentFirst = new ArrayList<>(history.reversed()); return recentFirst; } public static void main(String[] args) { NavigationHistory nav = new NavigationHistory(5); nav.visitPage("home"); nav.visitPage("products"); nav.visitPage("product-details"); nav.visitPage("cart"); nav.visitPage("checkout"); System.out.println("History (oldest first): " + nav.getHistory()); System.out.println("History (recent first): " + nav.getRecentFirst()); // Go back String previous = nav.goBack(); System.out.println("Went back to: " + previous); System.out.println("Current history: " + nav.getHistory()); // Visit new page nav.visitPage("confirmation"); System.out.println("After new visit: " + nav.getHistory()); } }

Migration Guide

Before Java 21 vs After Java 21

Before Java 21:

// Inconsistent APIs List<String> list = new ArrayList<>(); String first = list.get(0); // or list.isEmpty() ? null : list.get(0) Deque<String> deque = new ArrayDeque<>(); String first = deque.getFirst(); SortedSet<String> sortedSet = new TreeSet<>(); String first = sortedSet.first(); LinkedHashSet<String> linkedSet = new LinkedHashSet<>(); // No direct way to get first/last!

After Java 21:

// Unified API SequencedCollection<String> collection = ...; String first = collection.getFirst(); String last = collection.getLast(); SequencedSet<String> set = ...; String first = set.getFirst(); SequencedMap<String, Integer> map = ...; Map.Entry<String, Integer> firstEntry = map.firstEntry();

Key Benefits

  1. Consistent API: Unified methods across different collection types
  2. Improved Readability: Clear intent when accessing collection endpoints
  3. Reversible Views: Built-in support for reversed iteration
  4. Reduced Boilerplate: No need for workarounds to access first/last elements
  5. Enhanced Functionality: Additional operations like addFirst, addLast

Implementation Support

SequencedSet implementations:

  • LinkedHashSet
  • TreeSet (read-only for mutation operations)
  • ConcurrentSkipListSet (read-only for mutation operations)

SequencedMap implementations:

  • LinkedHashMap
  • TreeMap (read-only for putFirst/putLast)
  • ConcurrentSkipListMap (read-only for putFirst/putLast)

Conclusion

Sequenced Collections in Java 21 provide:

  • Standardized endpoint access across collection types
  • Reversible views for bidirectional traversal
  • Consistent mutation operations where supported
  • Backward compatibility with existing collections

When to use SequencedSet/SequencedMap:

  • When you need consistent access to collection endpoints
  • For ordered collections where position matters
  • When building navigation systems, caches, or history tracking
  • For cleaner, more maintainable collection code

These new interfaces make Java collections more expressive and eliminate many common pain points when working with ordered collections.


Note: Sequenced Collections are a preview feature in Java 21. Make sure to enable preview features (--enable-preview) when compiling and running code that uses these interfaces.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper