Sequenced Collections in java 21: Practical Examples

In the world of Java programming, the introduction of Sequenced Collections in Java 21 has brought significant improvements to existing Collection classes and interfaces. This new feature allows easy access to both the first and last elements of a collection, thanks to the inclusion of default library methods. It also enables developers to obtain a reversed view of the collection with a simple method call.

It’s important to clarify that in this context, “encounter order” does not refer to the physical arrangement of elements within the collection. Instead, it means that one element can be positioned either before (closer to the first element) or after (closer to the last element) another element in the ordered sequence.

Let’s dive deeper into this exciting addition, which has been part of Java since the release of Java 21 JEP-431

These newly introduced interfaces are

  1. SequencedCollection
  2. SequencedSet
  3. SequencedMap

Now, let’s illustrate the power of Sequenced Collections in Java 21 with a practical example:

Example: Managing a Playlist Imagine you’re developing a music streaming application in Java. In this application, you need to maintain a playlist of songs, allowing users to navigate easily between tracks. The introduction of Sequenced Collections becomes incredibly valuable in this scenario.

By utilizing SequencedSet, you can ensure that songs in the playlist maintain a specific order, enabling users to move seamlessly from one song to the next or return to the previous one. Additionally, you can use SequencedCollection to manage song history, making it effortless for users to retrace their listening journey, starting from the first song they played to the most recent one.

This real-life example illustrates how Sequenced Collections in Java 21 can enhance the user experience and streamline the management of ordered data in your applications.

Sequenced Collections in java 21

Sequenced Collections in Java 21 Made Simple

The SequencedCollection interface introduces a set of methods to streamline the addition, retrieval, and removal of elements at both ends of a collection. It also offers the ‘reversed()’ method, which presents a reversed view of the collection. Worth noting is that, apart from ‘reversed()’, all these methods are default methods, accompanied by default implementations

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

    SequencedCollection<E> reversed();
    default void addFirst(E e) {
    }
    default void addLast(E e) {
    }
    default E getFirst() {
    }
    default E getLast() {
    }
    default E removeFirst() {
    }
    default E removeLast() {
    }
}

For instance, consider the following code snippet where we create an ArrayList and perform new sequenced operations on it:

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

list.add(10);          // Adds 10 to the list.
list.addFirst(0);      // Adds 0 to the beginning of the list.
list.addLast(20);      // Adds 20 to the end of the list.
System.out.println("list: " + list);        // Output: list: [0, 10, 20]
System.out.println(list.getFirst());         // Output: 0
System.out.println(list.getLast());          // Output: 20
System.out.println(list.reversed());        // Output: [20, 10, 0]

This code demonstrates how Sequenced Collections simplify the management of ordered data within a collection, offering easy access to elements at both ends and providing a convenient method to view the collection in reverse order.

SequencedSet: Streamlined Collection Sorting

The SequencedSet interface is designed specifically for Set implementations, such as LinkedHashSet. It builds upon the SequencedCollection interface while customizing the ‘reversed()’ method. The key distinction lies in the return type of ‘SequencedSet.reversed()’, which is now ‘SequencedSet’.

Sequencedset.class

public interface SequencedSet<E> extends SequencedCollection<E>, Set<E> {
    SequencedSet<E> reversed();  // Overrides and specifies the return type for reversed() method.
}

Example: Using SequencedSet

Let’s explore an example of how to utilize SequencedSet with LinkedHashSet:

import java.util.*;


public class SequencedSetExample {

    public static void main(String[] args) {
      LinkedHashSet<Integer> hashSet = new LinkedHashSet<>(List.of(5, 8, 12, 9, 10));

      System.out.println("LinkedHashSet contents: " + hashSet); // Output: [5, 8, 12, 9, 10]
      // First element in the LinkedHashSet.
      System.out.println("First element: " + hashSet.getFirst()); // Output: 5
      
      // Last element in the LinkedHashSet.
      System.out.println("Last element: " + hashSet.getLast()); // Output: 10
      
      // reversed view of the LinkedHashSet.
      System.out.println("Reversed view: " + hashSet.reversed()); // Output: [10, 9, 12, 8, 5]
    }

}

When you run this class, you’ll see the following output:

YAML
LinkedHashSet contents: [5, 8, 12, 9, 10]
First element: 5
Last element: 10
Reversed view: [10, 9, 12, 8, 5]

SequencedMap: Changing How Maps Are Ordered

Understanding SequencedMap

SequencedMap is a specialized interface designed for Map classes like LinkedHashMap, introducing a novel approach to managing ordered data within maps. Unlike SequencedCollection, which handles individual elements, SequencedMap offers its unique methods that manipulate map entries while considering their access order.

Exploring SequencedMap Features

SequencedMap introduces a set of default methods to enhance map entry management:

  • firstEntry(): Retrieves the first entry in the map.
  • lastEntry(): Retrieves the last entry in the map.
  • pollFirstEntry(): Removes and returns the first entry in the map.
  • pollLastEntry(): Removes and returns the last entry in the map.
  • putFirst(K k, V v): Inserts an entry at the beginning of the map.
  • putLast(K k, V v): Inserts an entry at the end of the map.
  • reversed(): Provides a reversed view of the map.
  • sequencedEntrySet(): Returns a SequencedSet of map entries, maintaining the encounter order.
  • sequencedKeySet(): Returns a SequencedSet of map keys, reflecting the encounter order.
  • sequencedValues(): Returns a SequencedCollection of map values, preserving the encounter order.

Example: Utilizing SequencedMap

LinkedHashMap<Integer, String> hashMap = new LinkedHashMap<>();
        hashMap.put(10, "Ten");
        hashMap.put(20, "Twenty");
        hashMap.put(30, "Thirty");
        hashMap.put(40, "Fourty");
        hashMap.put(50, "Fifty");

        System.out.println("hashmap: " + hashMap);
        // Output: {10=Ten, 20=Twenty, 30=Thirty, 40=Fourty, 50=Fifty}

        hashMap.put(0, "Zero");
        hashMap.put(100, "Hundred");

        System.out.println(hashMap); // {10=Ten, 20=Twenty, 30=Thirty, 40=Fourty, 50=Fifty, 0=Zero, 100=Hundred}

        // Fetching the first entry
        System.out.println("Fetching first entry: " + hashMap.entrySet().iterator().next());
        // Output: Fetching the first entry: 10=Ten

        // Fetching the last entry
        Entry<Integer, String> lastEntry = null;
        for (java.util.Map.Entry<Integer, String> entry : hashMap.entrySet()) {

In the traditional approach, prior to Java 21, working with a LinkedHashMap to manage key-value pairs involved manual iteration and manipulation of the map. Here’s how it was done

LinkedHashMap<Integer, String> hashMap = new LinkedHashMap<>();
        hashMap.put(10, "Ten");
        hashMap.put(20, "Twenty");
        hashMap.put(30, "Thirty");
        hashMap.put(40, "Fourty");
        hashMap.put(50, "Fifty");

        System.out.println("hashmap: " + hashMap);
        // Output: {10=Ten, 20=Twenty, 30=Thirty, 40=Fourty, 50=Fifty}

        hashMap.put(0, "Zero");
        hashMap.put(100, "Hundred");

        System.out.println(hashMap); // {10=Ten, 20=Twenty, 30=Thirty, 40=Fourty, 50=Fifty, 0=Zero, 100=Hundred}


        // Fetching the first entry
        System.out.println("Fetching first entry: " + hashMap.entrySet().iterator().next());
        // Output: Fetching the first entry: 10=Ten


        // Fetching the last entry
        Entry<Integer, String> lastEntry = null;
        for (java.util.Map.Entry<Integer, String> entry : hashMap.entrySet()) {
            lastEntry = entry;
        }
        System.out.println("Fetching last entry: " + lastEntry); // Output: Fetching the last entry: 100=Hundred


        // Removing the first entry
        Entry<Integer, String> removedFirstEntry = hashMap.entrySet().iterator().next();
        hashMap.remove(removedFirstEntry.getKey());
        System.out.println("Removing first entry: " + removedFirstEntry);
        // Output: Removing the first entry: 10=Ten


        hashMap.remove(lastEntry.getKey());
        System.out.println("Removing last entry: " + lastEntry);
        // Output: Removing the last entry: 100=Hundred


        System.out.println("hashMap: " + hashMap);
        // Output after removals: {20=Twenty, 30=Thirty, 40=Fourty, 50=Fifty, 0=Zero}


        LinkedHashMap<Integer, String> reversedMap = new LinkedHashMap<>();
        List<Entry<Integer, String>> entries = new ArrayList<>(hashMap.entrySet());

        Collections.reverse(entries);

        for (Entry<Integer, String> entry : entries) {
            reversedMap.put(entry.getKey(), entry.getValue());
        }


        System.out.println("Reversed view of the map: " + reversedMap);
        // Output: Reversed view of the map: {50=Fifty, 40=Fourty, 30=Thirty, 20=Twenty,
        // 10=Ten}

However, in Java 21, with the introduction of sequenced collections, managing a LinkedHashMap has become more convenient. Here’s the updated code that demonstrates this.

import java.util.LinkedHashMap;


public class SequencedMapExample {
 
    public static void main(String[] args) {
        
       LinkedHashMap<Integer, String> hashMap = new LinkedHashMap<>();
       hashMap.put(10, "Ten");
       hashMap.put(20, "Twenty");
       hashMap.put(30, "Thirty");
       hashMap.put(40, "Fourty");
       hashMap.put(50, "Fifty");

       System.out.println(hashMap); 
       // Output: {10=Ten, 20=Twenty, 30=Thirty, 40=Fourty, 50=Fifty}

       hashMap.putFirst(0, "Zero");
       hashMap.putLast(100, "Hundred");
       
       System.out.println(hashMap); 
       // Output after adding elements at the beginning and end:
       // {0=Zero, 10=Ten, 20=Twenty, 30=Thirty, 40=Fourty, 50=Fifty, 100=Hundred}

       System.out.println("Fetching first entry: " + hashMap.firstEntry());
       // Fetching the first entry: 0=Zero

       System.out.println("Fetching last entry: " + hashMap.lastEntry());
       // Fetching the last entry: 100=Hundred
        
       System.out.println("Removing first entry: " + hashMap.pollFirstEntry());
       // Removing the first entry: 0=Zero

       System.out.println("Removing last entry: " + hashMap.pollLastEntry());
       // Removing the last entry: 100=Hundred

       System.out.println("hashMap: " + hashMap);
       // Output after removals: {10=Ten, 20=Twenty, 30=Thirty, 40=Fourty, 50=Fifty}

       System.out.println("Reversed: " + hashMap.reversed());
       // Reversed view of the map: {50=Fifty, 40=Fourty, 30=Thirty, 20=Twenty, 10=Ten}
    }
}

Conclusion: Simplifying Java 21

Sequenced collections are a valuable addition to Java 21, enhancing the language’s ease of use. These features simplify collection management, making coding in Java 21 even more accessible and efficient. Enjoy the benefits of these enhancements in your Java 21 development journey!

Leave a Comment