google-site-verification: google61fe8ba583a51912.html
Orderly Evolution: Java 21 Sequenced Collections

Java 21 introduces Sequenced Collections - a revolutionary approach to handling ordered collections with consistent, intuitive APIs across all collection types. This feature addresses long-standing inconsistencies in Java's collection framework.


The Problem: Inconsistent Ordered Collection APIs

Before Java 21, working with ordered collections was frustratingly inconsistent:

// Different APIs for similar operations across collections List<String> list = new ArrayList<>(); list.add("first"); String firstList = list.get(0); // get first String lastList = list.get(list.size() - 1); // get last - verbose! Deque<String> deque = new ArrayDeque<>(); deque.addFirst("first"); String firstDeque = deque.getFirst(); // get first String lastDeque = deque.getLast(); // get last // No direct way to get first/last from SortedSet SortedSet<String> sortedSet = new TreeSet<>(); sortedSet.add("apple"); sortedSet.add("banana"); // How to get first efficiently? String firstSorted = sortedSet.iterator().next(); // Inefficient!

Introducing Sequenced Collections

Java 21 adds new interfaces that provide consistent ordering operations:

Collection Hierarchy with Sequenced Collections: ┌─────────────────┐ │ SequencedCollection │ ← New interface └─────────────────┘ △ │ ┌─────────────────┐ │ List │ └─────────────────┘ △ │ ┌─────────────────┐ │ SequencedSet │ ← New interface └─────────────────┘ △ │ ┌─────────────────┐ │ SortedSet │ └─────────────────┘ △ │ ┌─────────────────┐ │ NavigableSet │ └─────────────────┘ ┌─────────────────┐ │ SequencedMap │ ← New interface └─────────────────┘ △ │ ┌─────────────────┐ │ SortedMap │ └─────────────────┘ △ │ ┌─────────────────┐ │ NavigableMap │ └─────────────────┘

Core Sequenced Collection Interfaces

1. SequencedCollection Interface

public interface SequencedCollection<E> extends Collection<E> { // New methods for ordered access SequencedCollection<E> reversed(); void addFirst(E e); void addLast(E e); E getFirst(); E getLast(); E removeFirst(); E removeLast(); }

2. SequencedSet Interface

public interface SequencedSet<E> extends Set<E>, SequencedCollection<E> { // Override return type for better type safety SequencedSet<E> reversed(); }

3. SequencedMap Interface

public interface SequencedMap<K, V> extends Map<K, V> { // Map-specific sequenced operations SequencedMap<K, V> reversed(); SequencedSet<K> sequencedKeySet(); SequencedCollection<V> sequencedValues(); SequencedSet<Entry<K, V>> sequencedEntrySet(); Entry<K, V> firstEntry(); Entry<K, V> lastEntry(); Entry<K, V> pollFirstEntry(); Entry<K, V> pollLastEntry(); V putFirst(K key, V value); V putLast(K key, V value); }

Practical Usage Examples

1. Consistent List Operations

public class SequencedListExamples { public static void main(String[] args) { // Create a sequenced list SequencedCollection<String> list = new ArrayList<>(); // Add elements to both ends list.addFirst("first"); // ["first"] list.addLast("last"); // ["first", "last"] list.add("middle"); // ["first", "last", "middle"] // Access ends consistently System.out.println("First: " + list.getFirst()); // "first" System.out.println("Last: " + list.getLast()); // "middle" // Remove from ends String removedFirst = list.removeFirst(); // "first" String removedLast = list.removeLast(); // "middle" System.out.println("Remaining: " + list); // ["last"] // Reverse view SequencedCollection<String> reversed = list.reversed(); reversed.addFirst("new-first"); // Affects original list System.out.println("Original: " + list); // ["new-first", "last"] } }

2. Set Operations with Order

public class SequencedSetExamples { public static void main(String[] args) { // LinkedHashSet maintains insertion order SequencedSet<String> orderedSet = new LinkedHashSet<>(); orderedSet.add("apple"); orderedSet.add("banana"); orderedSet.addFirst("apricot"); // Insert at beginning orderedSet.addLast("cherry"); // Insert at end System.out.println("Set: " + orderedSet); // [apricot, apple, banana, cherry] System.out.println("First: " + orderedSet.getFirst()); // apricot System.out.println("Last: " + orderedSet.getLast()); // cherry // TreeSet with natural ordering SequencedSet<String> sortedSet = new TreeSet<>(); sortedSet.addAll(List.of("zebra", "apple", "monkey")); System.out.println("Sorted first: " + sortedSet.getFirst()); // apple System.out.println("Sorted last: " + sortedSet.getLast()); // zebra // Reverse view maintains performance SequencedSet<String> reversedSorted = sortedSet.reversed(); System.out.println("Reversed first: " + reversedSorted.getFirst()); // zebra } }

3. Map Operations with Order

public class SequencedMapExamples { public static void main(String[] args) { // LinkedHashMap maintains insertion order SequencedMap<String, Integer> map = new LinkedHashMap<>(); map.put("one", 1); map.put("three", 3); map.putFirst("zero", 0); // Insert at beginning map.putLast("four", 4); // Insert at end System.out.println("Map: " + map); // {zero=0, one=1, three=3, four=4} // Access first and last entries Map.Entry<String, Integer> firstEntry = map.firstEntry(); Map.Entry<String, Integer> lastEntry = map.lastEntry(); System.out.println("First: " + firstEntry.getKey() + "=" + firstEntry.getValue()); System.out.println("Last: " + lastEntry.getKey() + "=" + lastEntry.getValue()); // Poll entries (remove and return) Map.Entry<String, Integer> polledFirst = map.pollFirstEntry(); Map.Entry<String, Integer> polledLast = map.pollLastEntry(); System.out.println("After polling: " + map); // {one=1, three=3} // Sequenced views SequencedSet<String> keys = map.sequencedKeySet(); SequencedCollection<Integer> values = map.sequencedValues(); SequencedSet<Map.Entry<String, Integer>> entries = map.sequencedEntrySet(); System.out.println("Keys: " + keys.getFirst() + " to " + keys.getLast()); } }

Real-World Use Cases

1. LRU (Least Recently Used) Cache

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 getMostRecent() { return cache.getLast(); // Most recently accessed } public V getLeastRecent() { return cache.getFirst(); // Least recently accessed } public void removeOldest() { cache.pollFirstEntry(); // Remove least recently used } public SequencedSet<K> getKeysInAccessOrder() { return cache.sequencedKeySet(); } }

2. Message Queue with Priority Handling

public class MessageProcessor { private final SequencedCollection<Message> highPriorityQueue = new ArrayDeque<>(); private final SequencedCollection<Message> normalQueue = new ArrayDeque<>(); public void addHighPriority(Message message) { highPriorityQueue.addLast(message); } public void addNormalPriority(Message message) { normalQueue.addLast(message); } public Message getNextMessage() { // Process high priority first, then normal if (!highPriorityQueue.isEmpty()) { return highPriorityQueue.removeFirst(); } else if (!normalQueue.isEmpty()) { return normalQueue.removeFirst(); } return null; } public Message peekNextMessage() { if (!highPriorityQueue.isEmpty()) { return highPriorityQueue.getFirst(); } else if (!normalQueue.isEmpty()) { return normalQueue.getFirst(); } return null; } public void returnToFront(Message message) { // Re-queue important message at front highPriorityQueue.addFirst(message); } }

3. Navigation History

public class NavigationHistory { private final SequencedCollection<String> history = new ArrayDeque<>(); private final int maxHistorySize; private int currentIndex = -1; public NavigationHistory(int maxHistorySize) { this.maxHistorySize = maxHistorySize; } public void navigateTo(String page) { // Remove future history if we're not at the end if (currentIndex < history.size() - 1) { SequencedCollection<String> future = getFutureHistory(); future.clear(); } history.addLast(page); currentIndex = history.size() - 1; // Enforce size limit if (history.size() > maxHistorySize) { history.removeFirst(); currentIndex--; } } public String goBack() { if (currentIndex > 0) { currentIndex--; return history.reversed().stream() .skip(history.size() - currentIndex - 1) .findFirst() .orElseThrow(); } throw new IllegalStateException("Cannot go back"); } public String goForward() { if (currentIndex < history.size() - 1) { currentIndex++; return history.reversed().stream() .skip(history.size() - currentIndex - 1) .findFirst() .orElseThrow(); } throw new IllegalStateException("Cannot go forward"); } public SequencedCollection<String> getFutureHistory() { return history.reversed().stream() .limit(history.size() - currentIndex - 1) .collect(Collectors.toCollection(ArrayDeque::new)); } public String getCurrentPage() { if (currentIndex >= 0) { return history.reversed().stream() .skip(history.size() - currentIndex - 1) .findFirst() .orElseThrow(); } return null; } }

Performance Characteristics

1. Collection Performance Comparison

public class SequencedCollectionPerformance { public static void benchmarkOperations() { int size = 100000; // ArrayList performance SequencedCollection<Integer> arrayList = new ArrayList<>(); benchmark("ArrayList addFirst", () -> { for (int i = 0; i < size; i++) { arrayList.addFirst(i); // O(n) - shifts all elements! } }); // ArrayDeque performance SequencedCollection<Integer> arrayDeque = new ArrayDeque<>(); benchmark("ArrayDeque addFirst", () -> { for (int i = 0; i < size; i++) { arrayDeque.addFirst(i); // O(1) - circular buffer } }); // LinkedHashSet performance SequencedSet<Integer> linkedHashSet = new LinkedHashSet<>(); benchmark("LinkedHashSet addFirst", () -> { for (int i = 0; i < size; i++) { linkedHashSet.addFirst(i); // O(1) - linked list } }); } private static void benchmark(String name, Runnable operation) { long start = System.nanoTime(); operation.run(); long duration = System.nanoTime() - start; System.out.printf("%s: %d ms%n", name, duration / 1_000_000); } }

2. Choosing the Right Implementation

public class SequencedCollectionChooser { public static <E> SequencedCollection<E> createOptimalCollection(UsagePattern pattern) { return switch (pattern) { case FREQUENT_RANDOM_ACCESS -> { // ArrayList - good for get(index) but expensive addFirst/removeFirst yield new ArrayList<>(); } case FREQUENT_FIRST_LAST_OPS -> { // ArrayDeque - optimized for add/remove from both ends yield new ArrayDeque<>(); } case UNIQUE_ELEMENTS_WITH_ORDER -> { // LinkedHashSet - maintains insertion order, prevents duplicates yield new LinkedHashSet<>(); } case SORTED_ELEMENTS -> { // TreeSet - maintains natural ordering yield new TreeSet<>(); } }; } public enum UsagePattern { FREQUENT_RANDOM_ACCESS, FREQUENT_FIRST_LAST_OPS, UNIQUE_ELEMENTS_WITH_ORDER, SORTED_ELEMENTS } }

Migration from Legacy Code

1. Converting Legacy Collection Code

public class LegacyMigration { // Before Java 21 - inconsistent approaches public static <T> T getFirstElementLegacy(Collection<T> collection) { if (collection instanceof List<T> list) { return list.get(0); } else if (collection instanceof Deque<T> deque) { return deque.getFirst(); } else if (collection instanceof SortedSet<T> sortedSet) { return sortedSet.first(); } else { return collection.iterator().next(); } } // After Java 21 - unified approach public static <T> T getFirstElementModern(Collection<T> collection) { if (collection instanceof SequencedCollection<T> sequenced) { return sequenced.getFirst(); } else { return collection.iterator().next(); } } // Handling maps public static <K, V> V getFirstValueLegacy(Map<K, V> map) { if (map instanceof SortedMap<K, V> sortedMap) { return sortedMap.get(sortedMap.firstKey()); } else if (map instanceof LinkedHashMap<K, V> linkedMap) { return linkedMap.entrySet().iterator().next().getValue(); } else { throw new UnsupportedOperationException("Map doesn't support ordering"); } } public static <K, V> V getFirstValueModern(Map<K, V> map) { if (map instanceof SequencedMap<K, V> sequencedMap) { return sequencedMap.firstEntry().getValue(); } else { throw new UnsupportedOperationException("Map doesn't support ordering"); } } }

2. Backward Compatibility

public class CompatibilityLayer { // For code that needs to work with pre-Java 21 and Java 21+ public static <E> void addToBeginning(Collection<E> collection, E element) { if (collection instanceof SequencedCollection<E> sequenced) { sequenced.addFirst(element); } else if (collection instanceof List<E> list) { list.add(0, element); } else if (collection instanceof Deque<E> deque) { deque.addFirst(element); } else { // Fallback - create new collection List<E> newList = new ArrayList<>(collection); newList.add(0, element); collection.clear(); collection.addAll(newList); } } // Safe first element access public static <E> Optional<E> getFirstSafe(Collection<E> collection) { if (collection instanceof SequencedCollection<E> sequenced) { try { return Optional.of(sequenced.getFirst()); } catch (NoSuchElementException e) { return Optional.empty(); } } else if (!collection.isEmpty()) { return Optional.of(collection.iterator().next()); } else { return Optional.empty(); } } }

Advanced Patterns and Recipes

1. Circular Buffer Implementation

public class CircularBuffer<E> implements SequencedCollection<E> { private final SequencedCollection<E> delegate; private final int capacity; public CircularBuffer(int capacity) { this.capacity = capacity; this.delegate = new ArrayDeque<>(capacity); } @Override public boolean add(E e) { if (delegate.size() == capacity) { delegate.removeFirst(); } return delegate.add(e); } @Override public void addFirst(E e) { if (delegate.size() == capacity) { delegate.removeLast(); } delegate.addFirst(e); } @Override public void addLast(E e) { add(e); } // Delegate all other SequencedCollection methods @Override public E getFirst() { return delegate.getFirst(); } @Override public E getLast() { return delegate.getLast(); } @Override public E removeFirst() { return delegate.removeFirst(); } @Override public E removeLast() { return delegate.removeLast(); } @Override public SequencedCollection<E> reversed() { return delegate.reversed(); } // Implement other Collection methods... @Override public int size() { return delegate.size(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } @Override public Iterator<E> iterator() { return delegate.iterator(); } // ... remaining Collection methods }

2. Priority-Aware Collection

public class PrioritySequencedCollection<E> implements SequencedCollection<E> { private final SequencedCollection<E> highPriority = new ArrayDeque<>(); private final SequencedCollection<E> normalPriority = new ArrayDeque<>(); @Override public boolean add(E e) { return addLast(e); } @Override public void addFirst(E e) { highPriority.addFirst(e); } @Override public void addLast(E e) { normalPriority.addLast(e); } @Override public E getFirst() { if (!highPriority.isEmpty()) { return highPriority.getFirst(); } else if (!normalPriority.isEmpty()) { return normalPriority.getFirst(); } throw new NoSuchElementException(); } @Override public E getLast() { if (!normalPriority.isEmpty()) { return normalPriority.getLast(); } else if (!highPriority.isEmpty()) { return highPriority.getLast(); } throw new NoSuchElementException(); } @Override public E removeFirst() { if (!highPriority.isEmpty()) { return highPriority.removeFirst(); } else if (!normalPriority.isEmpty()) { return normalPriority.removeFirst(); } throw new NoSuchElementException(); } @Override public E removeLast() { if (!normalPriority.isEmpty()) { return normalPriority.removeLast(); } else if (!highPriority.isEmpty()) { return highPriority.removeLast(); } throw new NoSuchElementException(); } @Override public SequencedCollection<E> reversed() { // Return a reversed view that maintains priority semantics return new ReversedPriorityView(); } private class ReversedPriorityView implements SequencedCollection<E> { // Implementation of reversed view... } // Other collection methods... }

Testing Sequenced Collections

1. Comprehensive Test Suite

class SequencedCollectionTest { @Test void testAddFirstAndGetFirst() { SequencedCollection<String> collection = new ArrayList<>(); collection.addFirst("first"); collection.addLast("last"); assertEquals("first", collection.getFirst()); assertEquals("last", collection.getLast()); } @Test void testRemoveFirstAndLast() { SequencedCollection<Integer> collection = new ArrayDeque<>(); collection.addAll(List.of(1, 2, 3, 4)); assertEquals(1, collection.removeFirst()); assertEquals(4, collection.removeLast()); assertEquals(List.of(2, 3), new ArrayList<>(collection)); } @Test void testReversedView() { SequencedCollection<String> original = new LinkedHashSet<>(); original.addAll(List.of("a", "b", "c")); SequencedCollection<String> reversed = original.reversed(); assertEquals("c", reversed.getFirst()); assertEquals("a", reversed.getLast()); // Modifications through reversed view affect original reversed.addFirst("d"); assertEquals("d", original.getLast()); } @ParameterizedTest @MethodSource("collectionImplementations") void testSequencedContract(SequencedCollection<Integer> collection) { collection.addFirst(1); collection.addLast(2); assertEquals(1, collection.getFirst()); assertEquals(2, collection.getLast()); assertEquals(1, collection.removeFirst()); assertEquals(2, collection.removeLast()); assertTrue(collection.isEmpty()); } static Stream<SequencedCollection<Integer>> collectionImplementations() { return Stream.of( new ArrayList<>(), new LinkedList<>(), new ArrayDeque<>(), new LinkedHashSet<>() ); } }

Best Practices

1. Choose the Right Implementation

public class SequencedCollectionBestPractices { public void implementationGuidelines() { // Use cases and recommended implementations: // Frequent random access + sequenced operations SequencedCollection<String> forRandomAccess = new ArrayList<>(); // Pros: Fast get(index), good iteration // Cons: Expensive addFirst/removeFirst (O(n)) // Frequent add/remove from both ends SequencedCollection<String> forEndOperations = new ArrayDeque<>(); // Pros: O(1) addFirst/removeFirst, good memory locality // Cons: No random access // Unique elements with insertion order SequencedSet<String> forUniqueOrdered = new LinkedHashSet<>(); // Pros: Prevents duplicates, maintains order // Cons: No random access, memory overhead // Sorted elements SequencedSet<String> forSorted = new TreeSet<>(); // Pros: Automatic sorting, efficient range operations // Cons: Slower insertion (O(log n)) } public void performanceConsiderations() { // ArrayList - be careful with addFirst/removeFirst SequencedCollection<Integer> arrayList = new ArrayList<>(); // This is O(n) per operation! for (int i = 0; i < 100000; i++) { arrayList.addFirst(i); // Each call shifts all elements! } // Use ArrayDeque instead for this pattern SequencedCollection<Integer> arrayDeque = new ArrayDeque<>(); for (int i = 0; i < 100000; i++) { arrayDeque.addFirst(i); // O(1) per operation } } }

Conclusion

Java 21 Sequenced Collections provide a much-needed unification of ordered collection APIs:

Key Benefits:

  • Consistent API across all ordered collections
  • Intuitive operations for first/last element access
  • Performance clarity with appropriate implementation choices
  • Backward compatibility through new interfaces
  • Enhanced readability with fluent method names

Migration Strategy:

  1. Update type declarations to use sequenced interfaces where appropriate
  2. Replace custom first/last logic with standardized methods
  3. Choose optimal implementations based on usage patterns
  4. Leverage reversed views instead of manual reversal
  5. Update tests to verify sequenced behavior

Example of Modern Code:

public class ModernSequencedUsage { public void processTasks(SequencedCollection<Task> tasks) { // Process highest priority task first Task highestPriority = tasks.getFirst(); process(highestPriority); // Add new urgent task to front tasks.addFirst(createUrgentTask()); // Remove completed task from end Task completed = tasks.removeLast(); archive(completed); // Process in reverse order when needed for (Task task : tasks.reversed()) { if (needsRecheck(task)) { recheck(task); } } } }

Sequenced Collections make Java's collection framework more consistent, expressive, and powerful, finally providing a unified way to work with ordered data structures across the entire ecosystem.

Leave a Reply

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


Macro Nepal Helper