Order Meets Interface: LinkedHashMap as a SequencedMap in Modern Java

For years, LinkedHashMap has been Java's go-to ordered Map implementation, cherished for its predictable iteration order. However, its API for accessing ends of the map has always been awkward—until now. With the introduction of JEP 431: Sequenced Collections in Java 21, LinkedHashMap gained a powerful new identity by implementing the SequencedMap interface, finally providing a clean, standardized way to interact with its ordering.

The Problem: The Missing Order API

Traditionally, if you wanted to access the "first" or "last" element of a LinkedHashMap, you had to resort to inefficient workarounds:

// The old, inefficient way
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
// ... add elements ...
// Get first entry - O(n) traversal!
Map.Entry<String, Integer> firstEntry = map.entrySet().iterator().next();
// Get last entry - even worse!
Map.Entry<String, Integer> lastEntry = null;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
lastEntry = entry;
}
// Remove first element - equally painful
if (!map.isEmpty()) {
Map.Entry<String, Integer> first = map.entrySet().iterator().next();
map.remove(first.getKey());
}

This was cumbersome, inefficient (O(n) for accessing ends), and error-prone.

The Solution: SequencedMap Interface

Java 21's SequencedMap interface provides a unified contract for maps that maintain a well-defined encounter order. LinkedHashMap now implements this interface, offering direct, O(1) access to both ends of the map.

The enhanced LinkedHashMap now provides a comprehensive set of ordered operations, as shown in the following table:

OperationMethodDescriptionComplexity
Get first elementfirstEntry()Returns first key-value mappingO(1)
Get last elementlastEntry()Returns last key-value mappingO(1)
Get first keyfirstKey()Returns first keyO(1)
Get last keylastKey()Returns last keyO(1)
Add to frontputFirst(K, V)Inserts mapping at beginningO(1)
Add to endputLast(K, V)Inserts mapping at endO(1)
Remove firstpollFirstEntry()Removes and returns first entryO(1)
Remove lastpollLastEntry()Removes and returns last entryO(1)
Reverse viewreversed()Returns reverse-ordered viewO(1)

Practical Usage Examples

1. Basic Sequenced Operations

// Create a LinkedHashMap - now a SequencedMap!
SequencedMap<String, Integer> scores = new LinkedHashMap<>();
// Add elements (maintains insertion order by default)
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Charlie", 92);
System.out.println(scores); // {Alice=95, Bob=87, Charlie=92}
// Access ends efficiently
Map.Entry<String, Integer> first = scores.firstEntry();
Map.Entry<String, Integer> last = scores.lastEntry();
System.out.println("First: " + first); // First: Alice=95
System.out.println("Last: " + last);   // Last: Charlie=92
// Add elements at specific positions
scores.putFirst("Zoe", 99);  // Add at beginning
scores.putLast("David", 88); // Add at end
System.out.println(scores); // {Zoe=99, Alice=95, Bob=87, Charlie=92, David=88}
// Remove from ends
Map.Entry<String, Integer> removedFirst = scores.pollFirstEntry();
Map.Entry<String, Integer> removedLast = scores.pollLastEntry();
System.out.println("Removed first: " + removedFirst); // Removed first: Zoe=99
System.out.println("Removed last: " + removedLast);   // Removed last: David=88
System.out.println(scores); // {Alice=95, Bob=87, Charlie=92}

2. Reverse Iteration and Processing

SequencedMap<String, LocalDate> projectMilestones = new LinkedHashMap<>();
projectMilestones.put("Design", LocalDate.of(2024, 1, 15));
projectMilestones.put("Implementation", LocalDate.of(2024, 2, 28));
projectMilestones.put("Testing", LocalDate.of(2024, 3, 31));
// Process in chronological order (insertion order)
System.out.println("Chronological order:");
for (Map.Entry<String, LocalDate> entry : projectMilestones.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// Process in reverse chronological order
System.out.println("\nReverse chronological order:");
SequencedMap<String, LocalDate> reversed = projectMilestones.reversed();
for (Map.Entry<String, LocalDate> entry : reversed.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// Output:
// Chronological order:
// Design: 2024-01-15
// Implementation: 2024-02-28  
// Testing: 2024-03-31
//
// Reverse chronological order:
// Testing: 2024-03-31
// Implementation: 2024-02-28
// Design: 2024-01-15

3. Building an LRU (Least Recently Used) Cache

LinkedHashMap has always been perfect for LRU caches, but now the API is even cleaner:

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int maxSize;
public LRUCache(int maxSize) {
// Access-order = true (LRU behavior), Insertion-order = false
super(maxSize, 0.75f, true);
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
// New sequenced operations enhance the cache API
public Map.Entry<K, V> getLeastRecent() {
return this.firstEntry(); // O(1) access to least recent
}
public Map.Entry<K, V> getMostRecent() {
return this.lastEntry(); // O(1) access to most recent
}
}
// Usage
LRUCache<String, String> cache = new LRUCache<>(3);
cache.put("A", "Data A");
cache.put("B", "Data B"); 
cache.put("C", "Data C");
// Access pattern affects order
cache.get("A"); // Makes A most recently used
System.out.println("Least recent: " + cache.getLeastRecent()); // B=Data B
System.out.println("Most recent: " + cache.getMostRecent());   // A=Data A
// Adding new item evicts least recent
cache.put("D", "Data D");
System.out.println(cache); // {C=Data C, A=Data A, D=Data D} - B was evicted

Advanced Patterns and Considerations

1. Access Order vs Insertion Order

LinkedHashMap supports two ordering modes, and SequencedMap works with both:

// Insertion-order (default)
LinkedHashMap<String, Integer> insertionOrder = new LinkedHashMap<>();
insertionOrder.put("Z", 1);
insertionOrder.put("A", 2);
insertionOrder.put("M", 3);
// Order: Z, A, M (as inserted)
// Access-order (for LRU caches)
LinkedHashMap<String, Integer> accessOrder = new LinkedHashMap<>(16, 0.75f, true);
accessOrder.put("Z", 1);
accessOrder.put("A", 2); 
accessOrder.put("M", 3);
accessOrder.get("Z"); // Access moves Z to end
// Order: A, M, Z (Z moved to end due to access)

2. Thread Safety Considerations

Like other Java collections, LinkedHashMap is not thread-safe. For concurrent access:

// Synchronized wrapper
SequencedMap<String, Integer> safeMap = Collections.synchronizedSequencedMap(
new LinkedHashMap<>()
);
// Or use ConcurrentHashMap (but it doesn't implement SequencedMap)
// For ordered concurrent maps, consider third-party libraries

3. Equality and Views

The reversed() view is backed by the original map:

LinkedHashMap<String, Integer> original = new LinkedHashMap<>();
original.put("A", 1);
original.put("B", 2);
SequencedMap<String, Integer> reversed = original.reversed();
System.out.println(reversed); // {B=2, A=1}
// Changes in original affect reversed view
original.put("C", 3);
System.out.println(reversed); // {C=3, B=2, A=1}
// reversed() is O(1) - it doesn't create a copy

Migration from Older Java Versions

If you're upgrading from pre-Java 21 code, the changes are largely additive:

// Pre-Java 21
LinkedHashMap<String, Integer> oldMap = new LinkedHashMap<>();
// ... populate map ...
Iterator<Map.Entry<String, Integer>> it = oldMap.entrySet().iterator();
Map.Entry<String, Integer> first = it.next(); // Inefficient
// Java 21+
SequencedMap<String, Integer> newMap = new LinkedHashMap<>();
// ... populate map ...
Map.Entry<String, Integer> first = newMap.firstEntry(); // Efficient O(1)

When to Use LinkedHashMap as SequencedMap

Perfect use cases:

  • LRU Caches: Natural fit with access-ordering
  • Ordered configurations: Properties that need to maintain order
  • Processing pipelines: Where element order matters
  • Recent items tracking: Maintaining most-recently-used lists
  • Session management: Tracking user actions in sequence

When to consider alternatives:

  • Unordered data: Use HashMap for better performance
  • Concurrent access: Consider ConcurrentHashMap (though it loses ordering)
  • Sorted data: Use TreeMap for natural ordering

Conclusion

The elevation of LinkedHashMap to a first-class SequencedMap citizen in Java 21 is more than just syntactic sugar—it's a fundamental improvement in API design. It eliminates the need for awkward, inefficient workarounds and provides a standardized contract for ordered map operations.

The SequencedMap interface finally gives LinkedHashMap the API it deserved all along, making ordered map operations intuitive, efficient, and self-documenting. Whether you're building caches, maintaining ordered configurations, or processing data sequences, the modern LinkedHashMap provides a clean, powerful solution that aligns with how developers naturally think about ordered collections.

This enhancement demonstrates Java's continued evolution toward more expressive, efficient, and intuitive APIs while maintaining backward compatibility—a win for both new and existing codebases.

Leave a Reply

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


Macro Nepal Helper