Sequenced Collections in Java 21 [JEP-431]

The Sequenced Collections is a new feature added in existing Java Collection classes/interfaces that allow them to access the first and the last element of it using library-provided default methods. The feature also allows us to get a reversed view of the collection with a simple method call.

Note that the encounter order does not imply the physical positioning of elements. It just means that one element is either before (closer to the first element) or after (closer to the last element) the other element.

Let us dive deep into this new feature added since Java 21.

1. Sequenced Collections Initiative

This new initiative [JEP-431] introduces 3 new interfaces in the collection hierarchy that allows the existing collection classes to have a defined encounter order. The order will have a well-defined first element, the second element, and so forth, up to the last element.

These new interfaces are SequencedCollection, SequencedSet and SequencedMap.

The motivation to introduce the sequenced interfaces is a long pending demand for simple methods to fetch the first and the last elements of a collection. Currently, before Java 21, if we want to get the first and the last elements of an ArrayList we would code:

var firstItem = arrayList.iterator().next(); 
var lastItem = arrayList.get(arrayList.size() - 1);

With the new sequenced collections, we can do the same thing using simpler methods:

var firstItem = arrayList.getFirst();
var lastItem = arrayList.getLast();

2. SequencedCollection

The SequencedCollection interface provides methods to add, retrieve, and remove elements at either end of the collection, along with the reversed method, which provides a reverse-ordered view of this collection.

As we can see all methods, except reversed(), are default methods and provide a default implementation.

default void addFirst(E e) 
default void addLast(E e)

default E getFirst()
default E getLast()

default E removeFirst()
default E removeLast()

SequencedCollection<E> reversed()

For example, the following program creates an ArrayList and performs new sequenced operations on this list.

ArrayList<Integer> arrayList = new ArrayList<>();

addFirst.add(1); 	// [1]

arrayList.addFirst(0);	// [0, 1]
arrayList.addLast(2);		// [0, 1, 2]

arrayList.getFirst();	// 0
arrayList.getLast();	// 2 

arrayList.reversed();	//[2, 1, 0]

Note that any modification to the list is visible in the methods, including the reversed view.

arrayList.add(3);

System.out.println( arrayList );	//[0, 1, 2, 3]
System.out.println( arrayList.reversed() );	//[3, 2, 1, 0]

3. SequencedSet

The SequencedSet interface is specific to Set implementations such as LinkedHashSet. SequencedSet extends SequencedCollection and overrides its reversed() method and the only difference is that the return type of SequencedSet.reversed() is SequencedSet.

SequencedSet<E> reversed()

Let us see an example of using these methods on sets.

LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet(List.of(1, 2, 3));

linkedHashSet.getFirst();   //1
linkedHashSet.getLast();    //3

linkedHashSet.addFirst(0);  //[0, 1, 2, 3]
linkedHashSet.addLast(4);   [0, 1, 2, 3, 4]

System.out.println(linkedHashSet.reversed());   //[5, 3, 2, 1, 0]

4. SequencedMap

The SequencedMap is specific to the Map classes, such as LinkedHashMap. It does not implement the SequencedCollection and provides its own methods that apply the access order to map entries in place of individual elements.

default Map.Entry<K,V> firstEntry()
default Map.Entry<K,V> lastEntry()

default Map.Entry<K,V> pollFirstEntry()
default Map.Entry<K,V> pollLastEntry()

default V putFirst(K k, V v)
default V putLast(K k, V v)

SequencedMap<K,V> reversed()

default SequencedSet<Map.Entry<K,V>> sequencedEntrySet()
default SequencedSet<K> sequencedKeySet()
default SequencedCollection<V> sequencedValues()

The view collections provided by the keySet(), values(), entrySet(), sequencedKeySet(), sequencedValues(), and sequencedEntrySet() methods all reflect the same encounter order of the elements. The difference is that the return values of the sequencedKeySet(), sequencedValues(), and sequencedEntrySet() methods are sequenced types.

LinkedHashMap<Integer, String> map = new LinkedHashMap<>();
map.put(1, "One");
map.put(2, "Two");
map.put(3, "Three");

map.firstEntry();   //1=One
map.lastEntry();    //3=Three

System.out.println(map);  //{1=One, 2=Two, 3=Three}

Map.Entry<Integer, String> first = map.pollFirstEntry();   //1=One
Map.Entry<Integer, String> last = map.pollLastEntry();    //3=Three

System.out.println(map);  //{2=Two}

map.putFirst(1, "One");     //{1=One, 2=Two}
map.putLast(3, "Three");    //{1=One, 2=Two, 3=Three}

System.out.println(map);  //{1=One, 2=Two, 3=Three}
System.out.println(map.reversed());   //{3=Three, 2=Two, 1=One}

5. UnsupportedOperationException

The new interfaces also affect the collection types that are unmodifiable. If we attempt to use the new add or remove operations in an unmodifiable collection, we will encounter the UnsupportedOperationException.

In the following example, the List is an unmodifiable type so we cannot use the methods such as addFirst(), addLast() etc.

List<Integer> list = List.of(1, 2, 3);

//list.addLast(4);  //Exception in thread "main" java.lang.UnsupportedOperationException

Similarly, some collections already have a defined sorting order and thus methods forcing the orders (e.g. addFirst(), addLast() etc) make no sense, invoking these methods will throw UnsupportedOperationException.

TreeSet<Integer> set = new TreeSet(List.of(1, 2, 3));

// set.addFirst(4);  //Exception in thread "main" java.lang.UnsupportedOperationException

6. NoSuchElementException

If we try to use the sequenced methods on an empty collection, we will get the NoSuchElementException, because there is no element present to return from the method.

List<Integer> list = List.of();

//list.getFirst();  //Exception in thread "main" java.lang.NoSuchElementException

7. Pitfalls

The sequenced collection changes have been integrated well into the collection framework, and the code that simply uses collections implementations will be largely unaffected. still, if our classes implement Collection interfaces to create custom types, a few incompatibilities may arise.

For example, if the existing custom implementations use the methods with the same name then naming conflict will arise and you may need to refactor the code when upgrading to Java 21.

Similarly, the List and Deque interface both provide covariant overrides of the reversed() method. The first method is returning List and the other returning Deque.

So if the custom class implements both interfaces, List and Deque, then it will start failing from Java 21.

class MyList implements List<T>, Deque<T> {	// This will fail in Java 21
	
	//...
}

The solution to fix this error is to define a new method reversed() in the MyList class and return a type that is the subtype of List as well as Deque. In our case, MyList implements both interfaces so we can return it from the reversed() method.

class MyList implements List<T>, Deque<T> {	// This will work
	
	//...

	MyList<E> reversed() {
		// implement the reversed method
	}
}

8. Conclusion

The sequenced collection is a great addition to make Java language more adaptable and easy to use for new developers. Most Java collections already had a defined encounter order through iterators, the new interfaces make it more official and provide direct-to-use methods for even more easier to interact with the items at both ends of a collection.

Happy Learning !!

Comments

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.