Java 21 Sequenced Collections

Since Java 21, Sequenced Collections is a new feature added to existing Collection classes/interfaces that allows them to access the first and the last elements of it using the new 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. What are Sequenced Collections?

This new initiative [JEP-431] introduces 3 new interfaces in the collection hierarchy that allow 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:

2. Motivation behind Sequenced Collections

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 get the first and the last elements using simpler methods:

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

3. The SequencedCollection Interface

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.

interface SequencedCollection<E> extends Collection<E> {

  // New Method

  SequencedCollection<E> reversed();

  // Promoted methods from Deque<E>

  void addFirst(E);
  void addLast(E);

  E getFirst();
  E getLast();

  E removeFirst();
  E removeLast();
}

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

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

arrayList.add(1);   // List contains: [1]

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

Integer firstElement = arrayList.getFirst();  // 0
Integer lastElement = arrayList.getLast();  // 2

List<Integer> reversed = arrayList.reversed();  
System.out.println(reversed); // Prints [2, 1, 0]

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

// previous code ...

arrayList.add(3);

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

4. The SequencedSet Interface

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.

interface SequencedSet<E> extends SequencedCollection<E>, Set<E> {

    SequencedSet<E> reversed();
}

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

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

Integer firstElement = linkedHashSet.getFirst();   // 1
Integer lastElement = linkedHashSet.getLast();    // 3

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

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

5. The SequencedMap Interface

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.

interface SequencedMap<K,V> extends Map<K,V> {

  // New Methods

  SequencedMap<K,V> reversed();

  SequencedSet<K> sequencedKeySet();
  SequencedCollection<V> sequencedValues();
  SequencedSet<Entry<K,V>> sequencedEntrySet();

  V putFirst(K, V);
  V putLast(K, V);

  // Promoted Methods from NavigableMap<K, V>
  
  Entry<K, V> firstEntry();
  Entry<K, V> lastEntry();
  
  Entry<K, V> pollFirstEntry();
  Entry<K, V> pollLastEntry();
}

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}

6. New Methods Added in Collections Class

The Collections class is a utility class that contains methods for common operations on collection types. One such operation is converting a mutable collection to an immutable one.

For example, Collections.unmodifiableList() method returns an unmodifiable view of the specified list. Matching the same theme, the Collections class has now three new methods that return immutable views for newly added types.

Collections.unmodifiableSequencedCollection(sequencedCollection);
Collections.unmodifiableSequencedSet(sequencedSet);
Collections.unmodifiableSequencedMap(sequencedMap);

7. Watch out for 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

8. NoSuchElementException on Empty Collections

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

9. 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 other 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
	}
}

10. 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 easier to interact with the items at both ends of a collection.

Happy Learning !!

Source Code on Github

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.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode