Sequenced Collections in Java 21

Sequenced Collections in Java 21

Discover the new sequenced collections, sets, and maps introduced in Java 21. Learn about their features, methods, and practical examples to enhance your Java projects.

1. Introduction

Java 21 introduces an exciting new feature: sequenced collections. This enhancement aims to simplify how developers interact with ordered data structures, providing a more consistent and flexible approach. In this blog post, we’ll delve into what sequenced collections, sets, and maps are, and explore the new methods introduced.

2. What Are Sequenced Collections?

Sequenced collections were introduced in Java 21 as part of JEP-431, offering data structures with guaranteed element order, easy access to first and last elements, and support for reversed iteration. This makes them ideal for applications where the order of elements is critical. Sequenced collections offer a unified approach to adding, removing, and accessing elements from both ends of the collection.

Key Features:

Sequenced collections in Java 21 offer several key features that enhance the way developers work with ordered data structures:

  1. Defined Encounter Order: Sequenced collections maintain a specific order of elements, ensuring predictable iteration and retrieval sequences.
  2. First and Last Element Access: They provide built-in default methods to easily access the first and last elements of a collection, streamlining common operations.
  3. Reversed View: Sequenced collections support obtaining a reversed view of the collection, allowing developers to iterate through elements in reverse order without manual manipulation.

These features collectively simplify operations on ordered data and improve code readability and efficiency.



3. Motivation for Sequenced Collections

Before Java 21, accessing the first and last elements in collections often involved cumbersome or inconsistent methods. For instance:

  • List: list.get(0) and list.get(list.size() - 1) for the first and last elements, respectively.
  • Deque: deque.getFirst() and deque.getLast() for the first and last elements.
  • SortedSet: sortedSet.first() and sortedSet.last() for the first and last elements.
  • LinkedHashSet: linkedHashSet.iterator().next() was the only way to access the first element, with no direct method for the last element.

These methods were not only inconvenient but also led to fragmented codebases and inconsistent APIs. Java 21 addresses these issues by introducing sequenced collections, which not only provide built-in default methods for accessing the first and last elements but also support a reversed view of the collection. For example:

  • SequencedCollection: sequencedCollection.getFirst() and sequencedCollection.getLast() for the first and last elements, respectively. Additionally, the reversed() method allows you to obtain a reversed view of the collection.
  • SequencedSet: Similarly offers sequencedSet.getFirst() and sequencedSet.getLast() methods along with a reversed() method for a reversed view.
  • SequencedMap: Includes sequencedMap.firstEntry() and sequencedMap.lastEntry() for accessing the first and last entries, with the added benefit of a reversed() method.

This enhancement simplifies operations and makes code more readable and consistent across different collection types, addressing the challenges posed by accessing elements and navigating collections in reverse order.



4. New Collection Interfaces in Java 21

Java 21 introduces three new interfaces to the collections framework to support sequenced collections:

  1. SequencedCollection
  2. SequencedSet
  3. SequencedMap

4.1 SequencedCollection Interface

A SequencedCollection extends the Collection interface and includes additional methods to handle elements at both ends and to retrieve a reversed view of the collection.

Interface Definition:

public interface SequencedCollection<E> extends Collection<E> {

    // New method for reversed view
    SequencedCollection<E> reversed();

    // Methods promoted from Deque
    default void addFirst(E e);
    default void addLast(E e);

    default E getFirst();
    default E getLast();

    default E removeFirst();
    default E removeLast();
}
SequencedCollection.java

Example:

import java.util.ArrayList;
import java.util.SequencedCollection;

public class SequencedCollectionExample {
    public static void main(String[] args) {

        SequencedCollection<String> sequenced = new ArrayList<>();
        sequenced.addFirst("1");
        sequenced.add("2");
        sequenced.add("3");
        sequenced.addLast("4");

        // Display elements
        sequenced.forEach(System.out::println);

        // Accessing first and last elements
        System.out.println("\nFirst Element: " + sequenced.getFirst());
        System.out.println("Last Element: " + sequenced.getLast());

        // Removing first and last elements
        sequenced.removeFirst();
        sequenced.removeLast();
        System.out.println("\nAfter removal:");
        sequenced.forEach(System.out::println);

        // Reversed view of the collection
        SequencedCollection<String> reversed = sequenced.reversed();
        System.out.println("\nReversed view:");
        reversed.forEach(System.out::println);
    }
}
SequencedCollectionExample.java

Output:

1
2
3
4

First Element: 1
Last Element: 4

After removal:
2
3

Reversed view:
3
2
cmd


4.2 SequencedSet Interface

A SequencedSet extends both Set and SequencedCollection interfaces, inheriting the same set of methods from SequencedCollection while also providing a covariant override for the reversed() method.

Interface Definition:

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

    // Covariant override for reversed view
    @Override
    SequencedSet<E> reversed(); 
    
    // Inherits addFirst, addLast, getFirst, getLast, removeFirst, removeLast from SequencedCollection
}
SequencedSet.java

Example:

import java.util.LinkedHashSet;
import java.util.SequencedSet;

public class SequencedSetExample {
    public static void main(String[] args) {
        
        SequencedSet<String> sequencedSet = new LinkedHashSet<>();
        sequencedSet.add("1");
        sequencedSet.add("2");
        sequencedSet.add("3");
        sequencedSet.add("4");

        // Display elements
        sequencedSet.forEach(System.out::println);

        // Accessing first and last elements
        System.out.println("\nFirst Element: " + sequencedSet.getFirst());
        System.out.println("Last Element: " + sequencedSet.getLast());

        // Removing first and last elements
        sequencedSet.removeFirst();
        sequencedSet.removeLast();
        System.out.println("\nAfter removal:");
        sequencedSet.forEach(System.out::println);

        // Reversed view
        SequencedSet<String> reversedSet = sequencedSet.reversed();
        System.out.println("\nReversed Set:");
        reversedSet.forEach(System.out::println);
    }
}
SequencedSetExample.java

Output:

1
2
3
4

First Element: 1
Last Element: 4

After removal:
2
3

Reversed Set:
3
2
cmd


4.3 SequencedMap Interface

A SequencedMap extends the Map interface and includes methods to handle entries at both ends, retrieve a reversed view of the map, manipulate entries at both ends, and access the first and last entries.

Interface Definition:

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

    // New methods for reversed view and sequenced collections
    SequencedMap<K, V> reversed();
    SequencedSet<K> sequencedKeySet();
    SequencedCollection<V> sequencedValues();
    SequencedSet<Map.Entry<K, V>> sequencedEntrySet();

    default V putFirst(K key, V value);
    default V putLast(K key, V value);

    // Methods promoted from NavigableMap
    default Map.Entry<K, V> firstEntry();
    default Map.Entry<K, V> lastEntry();
    default Map.Entry<K, V> pollFirstEntry();
    default Map.Entry<K, V> pollLastEntry();
}
SequencedMap.java

Example:

import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.SequencedCollection;
import java.util.SequencedMap;
import java.util.SequencedSet;

public class SequencedMapExample {

    public static void main(String[] args) {
        SequencedMap<String, String> sequencedMap = new LinkedHashMap<>();
        sequencedMap.putFirst("1", "One");
        sequencedMap.put("2", "Two");
        sequencedMap.put("3", "Three");
        sequencedMap.putLast("4", "Four");

        // Display entries
        sequencedMap.forEach((key, value) -> System.out.println(key + ": " + value));

        // Accessing first and last entries
        System.out.println("\nFirst Entry: " + sequencedMap.firstEntry());
        System.out.println("Last Entry: " + sequencedMap.lastEntry());

        // Removing first and last entries
        Entry<String, String> removedFirst = sequencedMap.pollFirstEntry();
        Entry<String, String> removedLast = sequencedMap.pollLastEntry();
        System.out.println("\nRemoved First Entry: " + removedFirst);
        System.out.println("Removed Last Entry: " + removedLast);
        System.out.println("After removal:");
        sequencedMap.forEach((key, value) -> System.out.println(key + ": " + value));

        // Reversed view
        SequencedMap<String, String> reversedMap = sequencedMap.reversed();
        System.out.println("\nReversed Map:");
        reversedMap.forEach((key, value) -> System.out.println(key + ": " + value));

        // Sequenced Key Set
        SequencedSet<String> sequencedKeySet = sequencedMap.sequencedKeySet();
        System.out.println("\nSequenced Key Set: " + sequencedKeySet);

        // Sequenced Entry Set
        SequencedSet<Entry<String, String>> sequencedEntrySet = sequencedMap.sequencedEntrySet();
        System.out.println("\nSequenced Entry Set: " + sequencedEntrySet);

        // Sequenced Values
        SequencedCollection<String> sequencedValues = sequencedMap.sequencedValues();
        System.out.println("Sequenced Values: " + sequencedValues);
    }
}
SequencedMapExample.java

Output:

1: One
2: Two
3: Three
4: Four

First Entry: 1=One
Last Entry: 4=Four

Removed First Entry: 1=One
Removed Last Entry: 4=Four
After removal:
2: Two
3: Three

Reversed Map:
3: Three
2: Two

Sequenced Key Set: [2, 3]

Sequenced Entry Set: [2=Two, 3=Three]
Sequenced Values: [Two, Three]
cmd


5. Enhanced Methods in Collections Class

The Collections class offers a set of utility methods for common operations on collections. Recently, new methods have been added to provide immutable views for newly introduced collection types.

New Immutable Views

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

These methods allow creating immutable views of sequenced collections, sets, and maps, ensuring data integrity and preventing modifications to the underlying collections.

6. Handling UnsupportedOperationException

It’s crucial to note that using these new methods on unmodifiable collections can lead to UnsupportedOperationException. For instance, attempting to add or remove elements from an unmodifiable collection will result in an exception.

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

//list.addLast(4);  //UnsupportedOperationException
Java

Similarly, collections with a defined sorting order won’t support certain operations, such as addFirst() or addLast(), which will also throw an UnsupportedOperationException.

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

// set.addFirst(4);  //UnsupportedOperationException
Java

7. Handling NoSuchElementException

In case of empty collections, using sequenced methods like getFirst() or getLast() will result in a NoSuchElementException as there are no elements present to return.

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

//emptyList.getFirst();  //NoSuchElementException
Java


8. Avoiding Naming Conflicts and Resolving Interface Incompatibilities

When upgrading to Java 21 and incorporating sequenced collections, be mindful of potential naming conflicts. If your custom implementations use methods with identical names to those introduced in the updated Java version, you may encounter issues that require code refactoring.

For example, consider a scenario where a custom class implements both the List and Deque interfaces. Since both interfaces provide covariant overrides of the reversed() method, conflicts can arise. In Java 21, this implementation will lead to failures.

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

To resolve this issue, define a new method reversed() within your custom class and ensure it returns a type that is a subtype of both List and Deque. By doing so, you can maintain compatibility with Java 21.

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

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

By addressing these potential pitfalls proactively, you can ensure a smooth transition to Java 21 and leverage the benefits of sequenced collections without encountering compatibility issues.



9. Things to consider

Here are some important considerations to keep in mind when working with sequenced collections:

  1. Compatibility: Ensure compatibility with existing codebases when migrating to Java 21 with sequenced collections.
  2. Method Conflicts: Be cautious of potential method conflicts if your custom classes implement multiple collection interfaces.
  3. Unsupported Operations: Keep in mind that certain operations like adding or removing elements may not be supported in specific scenarios, such as with unmodifiable collections or those with predefined sorting orders.
  4. Exception Handling: Be prepared to handle exceptions like UnsupportedOperationException and NoSuchElementException when using sequenced collections.

10. FAQs

How do sequenced collections differ from traditional collections?

Can sequenced collections be used with existing code?

What exceptions should I be aware of when using sequenced collections?

What are the key benefits of using sequenced collections?



11. Conclusion

In summary, Java 21’s sequenced collections mark a significant step forward in the Java Collections framework. These interfaces—SequencedCollection, SequencedSet, and SequencedMap bring default methods that make working with collections much easier and more versatile. They simplify tasks like accessing, adding, removing elements, and also offer the convenience of getting reversed views.

12. Learn More

#

Interested in learning more?

Efficiently Read Request Body Multiple Times in Spring Boot



Add a Comment

Your email address will not be published.