Looking at Java 21: Sequenced Collections
Dealing with Collections is improving with Java 21, as three new interfaces get retro-fitted right into the existing type hierarchies. These Sequenced Collections give us a uniform API to access the first and last elements, and process Collections in reverse.
Table of Contents
To better understand what Sequenced Collections are, let’s take a look a what Collections were before.
The Java Collections Framework
Introduced in Java 1.2, the Collections Framework gave use many interfaces and classes to represent groups of objects. This unified API has many advantages, like enabling interoperability between unrelated APIs with “standardized” Collection types, and therefore, fostering code reuse.
Over a dozen interfaces provide an excellent abstraction for the underlying general- and special-purpose implementations, including implementation for concurrent environments. Even though the type hierarchies and functionality of the different Collection types is well-thought-out and give a lot of flexibility and functionality, a certain aspect is missing… until now!
Front to Back, Back to Front
A quite common task with Collections is getting the first or last element, and usually, it’s not pretty… I’m pretty sure everyone reading this article has done the following countless times:
Some Collection types support accessing the first and last element directly, but there’s no shared interface with a common API (yet):
Collection Type | First Element | Last Element |
---|---|---|
List | list.get(0) | list.get(list.size() - 1) |
Deque | deque.getFirst() | deque.getLast() |
SortedSet | sortedSet.first() | sortedSet.last() |
The first element is also accessible through the Iterator<E>
, too, but it feels like a cheat:
Every Collection
-based type is easily traversable front to back, as it’s a descendant of Iterator
.
This way, we can use for-each
loops, Stream
pipelines, and create arrays from any collection by calling toArray()
.
For the inverse direction, back to front, there’s no easy way or trick available.
That means, for example, that if we have to deal with a LinkedHashSet
, we have to traverse the whole collection to get to the last element.
Until Java 21 and Sequenced Collections!
Sequenced Collection Types
JEP 431 introduces three new interfaces into the type hierarchy of the collection framework at strategic positions. Like many other features over the years, these interfaces inject their new functionality with the help of default methods, so everything is still backward compatible, and no one is forced to implement the methods by themselves unless we want a more specialized implementation.
The three new sequenced interfaces are:
SequencedCollection<E> extends Collection<E>
SequencedSet<E> extends SequencedCollection<E>, Set<E>
SequencedMap<K, V> extends Map<K, V>
SequencedCollection
The SequencedCollection<E>
interface looks like you would expect it, especially since all its methods, with the exception of reversed()
, were promoted from the pre-existing Deque<E>
type, to provide a known and uniform API:
The add...
and remove...
methods are optional and throw an UnsupportedOperationException
in their default implementation, to support unmodifiable collections.
The get...
methods behave like their brethren by throwing a NoSuchElementException
in the case of an empty collection.
SequencedSet
The SequencedSet<E>
is based on SequencedCollection<E>
but has a covariant override for the return type of the reversed()
method:
The mentioned behavior from before is also valid, as none of the default implementations from SequencedCollection<E>
are overridden.
SequencedMap<K, V>
Although differ conceptionally from other Collections as key-value-based constructs, there’s still a lot to gain from a sequenced approach.
Like with SequencedCollection<E>
, the type SequencedMap<K, V>
gains some of its functionality from promoting methods from a pre-existing type, in this case, NavigableMap<K, V>
:
An interesting addition is the two promoted poll...
methods, as they give us an easy way to access the first or last entry AND remove it in one swoop.
Extending Java’s Collection Framework
As mentioned before, the three new types are retrofitted right into the pre-existing type hierarchy, to give us all the new goodies without breaking compatibility in any way:
The Collection
-based types change as follows:
- Both
List
andDeque
now shareSequencedCollection
as their immediate super-interface. SortedSet
now is a direct descendant ofSequencedSet
- The implementation
LinkedHashSet
is now also aSequencedSet
. additionally implements SequencedSet
The changes for Map
were not as numerous, the type SequenceMap
is directly under Map
and above is the new super-interface for SorterMap
and an additional interface for the implementation LinkedHashMap
.
To match the general theme of the Collections Framework, there are also three new static
helper methods available on Collections
:
Collections.unmodifiableSequencedCollection(sequencedCollection)
Collections.unmodifiableSequencedSet(sequencedSet)
Collections.unmodifiableSequencedMap(sequencedMap)
Conclusion
The introduction of a well-defined encounter order and a uniform API across the board is a welcomed addition to Java in my opinion. It will provide developers with a more straightforward way to simplify commons collection tasks, and little by little, Java adds more convenience to its types.
As I talked about in previous articles, it might not be as concise and across-the-board compared to other languages, but implementing it in a straightforward and backward-compatible way is a trade-off I gladly accept.
Resources
Looking at Java 21
- Intro
- String Templates (JEP 430)
- Simpler Main Methods and Unnamed Classes (JEP 445)
- Sequenced Collections (JEP 431)
- Scoped Values (JEP 446)
- Switch Pattern Matching (JEP 441)
- Feature Deprecations (JEP 449, 451)
- Record Pattern Matching (JEP 440)
- Generational ZGC (JEP 439)
- Virtual Threads (JEP 444)
- The Little Things