Java 21 Features With Examples

Java 21 features with examples

Java 21 brings some exciting new features to the world of programming. In this article, we’ll explore these Java 21 features with practical examples to make your Java coding experience even better.

Please download OpenJDK 21 and add it to the PATH environment variable before switching to Java 21.

Java 21 Features:

1. Pattern Matching for Switch

Java 21 brings a powerful feature called Pattern Matching for Switch. It simplifies switch statements, making them more concise and readable. Check out an example:

Before java 21

// Before Java 21
String response = "yes";

switch (response) {
    case "yes":
    case "yeah":
        System.out.println("You said yes!");
        break;
    case "no":
    case "nope":
        System.out.println("You said no!");
        break;
    default:
        System.out.println("Please choose.");
}

In Java 21, you can rewrite the code provided above as follows:

// Java 21 Pattern Matching for Switch
String response = "yes";
switch (response) {
    case "yes", "yeah" -> System.out.println("You said yes!");
    case "no", "nope" -> System.out.println("You said no!");
    default -> System.out.println("Please choose.");
}

Explore more about Pattern Matching for Switch in the full article.

2. Unnamed Patterns and Variables

Java 21 introduces Unnamed Patterns and Variables, making your code more concise and expressive. Here is a short part to show you an example:

String userInput = "User Input"; 

try { 
    int number = Integer.parseInt(userInput);
    // Use 'number'
} catch (NumberFormatException ex) { 
    System.out.println("Invalid input: " + userInput);
}

Now, with Java 21, the above code can be rewritten as follows

String userInput = "User Input"; 

try { 
    int number = Integer.parseInt(userInput);
    // Use 'number'
} catch (NumberFormatException _) { 
    System.out.println("Invalid input: " + userInput);
}

In this updated version, we no longer use the ‘ex’ variable; instead, we’ve replaced it with an underscore (_). This simple change helps streamline the code and makes it more concise.

For a deep dive into this feature and more practical examples, visit the full article.

3. Unnamed Classes and Instance Main Methods

Java 21 introduces a fresh approach to defining classes and instance main methods right in your code. Let’s take a quick look at how this feature operates:

// Java 21 Examples of Classes Without Names and Main Methods Inside Instances
public class UnnamedClassesDemo {
    void main(String[] args) {
        System.out.println("Hello from an unnamed class!");
    }
}

Explore more about unnamed classes and instance main methods in the full article.

4. String Templates in Java

Java 21 introduces String Templates, simplifying string concatenation. Take a look:

// Java (using String.format)
String name = "Sachin P";
String message = String.format("Welcome %s", Java);

In Java 21, you can create a message using this syntax:

String name = "Sachin P";
String message = STR."Welcome  \{name}!";

Discover the power of string templates and practical examples in the full article.

5. Sequenced Collections in Java 21

Java 21 introduces Sequenced Collections, making it easier to work with ordered data. Here’s a glimpse:

List<Integer> list = new ArrayList<Integer>(); 
	list.add(0);
	list.add(1);
	list.add(2);
	
	// Fetch the first element (element at index 0)
	int firstElement = list.get(0);
	
	// Fetch the last element
	int lastElement = list.get(list.size() - 1);

In Java 21, you can retrieve elements using the following code.

List<Integer> list = new ArrayList<Integer>(); 
	list.add(0);
	list.add(1);
	list.add(2);
	
	// Fetch the first element (element at index 0)
	int firstElement = list.getFirst();
	
	// Fetch the last element
	int lastElement = list.getLast();

Learn more about SequencedCollection, SequencedSet and SequencedMap and explore practical examples in the full article.

To put it simply, Java 21 brings some exciting improvements to the language. Features like Unnamed Patterns and Variables, along with Pattern Matching for Switch, make coding easier and improve code readability. These enhancements make Java development more efficient and enjoyable. Java developers now have the tools to write cleaner and more expressive code, marking a significant step forward in the world of Java programming.

If you’re curious to explore more features and details about Java 21, I recommend checking out the official Java release notes available at this link: Java 21 Release Notes. These release notes provide comprehensive information about all the changes and enhancements introduced in Java 21.

Java 21 Pattern Matching for Switch Example

Java has been constantly evolving to meet the demands of modern programming. With the release of Java 21, a notable feature called Java 21 Pattern Matching for Switch has been introduced. In this article, we’ll explore what this feature is all about, how it works, and see some real-world examples to understand its practical applications.

Introducing Java 21’s Pattern Matching for Switch

Java 21 brings a significant improvement known as Pattern Matching for Switch, which revolutionizes the way we handle switch statements. This feature makes code selection more straightforward by letting us use patterns in case labels. It not only improves code readability but also reduces redundancy and simplifies complex switch statements.

How Pattern Matching Improves Switch Statements

Pattern Matching allows developers to utilize patterns in case labels, making it easier to match and extract components from objects. This eliminates the need for casting and repetitive instanceof checks, resulting in cleaner and more efficient code. Let’s dive into some practical examples to understand how Pattern Matching functions in real-world scenarios.

Practical Examples

Example 1: Matching a Specific Value

Consider a scenario where you need to categorize shapes based on their names. In traditional switch statements, you might do the following:

switch (shape) {
    case "circle":
        System.out.println("Handling circle logic");
        break;
    case "rectangle":
        System.out.println("Handling rectangle logic");
        break;
    case "triangle":
        System.out.println("Handling triangle logic");
        break;
    default:
        // Handle other cases
}

With Pattern Matching, you can simplify above code:

switch (shape) {
    case "circle" -> {
    	 System.out.println("Handling circle logic");
    }
    case "rectangle" -> {
        System.out.println("Handling rectangle logic");
    }
    case "triangle" -> {
       System.out.println("Handling triangle logic");
    }
    default -> {
        // Handle other cases
    }
}

Pattern Matching allows for a more concise and readable switch statement.

Example 2: Matching Complex Objects

Pattern Matching can also simplify code when dealing with complex objects. Suppose you have a list of vehicles, and you want to perform specific actions based on the vehicle type:

for (Object v : vehicles) {
    if (v instanceof Car) {
        Car car = (Car) v;
        //car logic
    } else if (v instanceof Scooter) {
        Scooter scooter = (Scooter) v;
        //scooter logic
    } else if (v instanceof Jeep) {
        Jeep jeep = (Jeep) v;
        //jeep logic
    }
}

The code above can be rewritten using Pattern Matching.

for (Object v : vehicles) {

    return switch (v) {
        case Car car -> {
            //car logic
        }
        case Scooter scooter -> {
            //scooter logic
        }
        case Jeep jeep -> {
            //jeep logic
        }
    }
}

Pattern Matching simplifies the code and eliminates the need for explicit casting.

Example 3: Java 21 – Handling Null Cases in Switch Statements

Before Java 21, switch statements and expressions posed a risk of throwing NullPointerExceptions when the selector expression was evaluated as null.

public void handleGreetings(String s) {
 // If 's' is null and we don't handle it, it will result in a NullPointerException.
    if (s == null) {
        System.out.println("No message available.");
        return;
    }
    switch (s) {
        case "Hello", "Hi" -> System.out.println("Greetings!");
        case "Bye" -> System.out.println("Goodbye!");
        default -> System.out.println("Same to you!");
    }
}

Java 21 Introduces a New Null Case Label. above code can rewritten like this

public void handleGreetingsInJava21(String s) {
    switch (s) {
        case null           -> System.out.println("No message available.");
        case "Hello", "Hi" -> System.out.println("Hello there!");
        case "Bye"         -> System.out.println("Goodbye!");
        default            -> System.out.println("Same to you!");
    }
}

Example 4: Java 21 Pattern Matching with Guards

In Java 21, pattern case labels can apply to multiple values, leading to conditional code on the right-hand side of a switch rule. However, we can now simplify our code using guards, allowing ‘when’ clauses in switch blocks to specify guards to pattern case labels.

Before Java 21.

public void testInput(String response) {

    switch (response) {
        case null -> System.out.println("No message available.");
        case String s when s.equalsIgnoreCase("MAYBE") -> {
            System.out.println("Not sure, please decide.");
        }
        case String s when s.equalsIgnoreCase("EXIT") -> {
            System.out.println("Exiting now.");
        }
        default -> System.out.println("Please retry.");
    }
}

Using Java 21 – Simplified Code

public void test21Input(String response) {

    switch (response) {
        case null -> System.out.println("No message available.");
        case String s when s.equalsIgnoreCase("MAYBE") -> {
            System.out.println("Not sure, please decide.");
        }
        case String s when s.equalsIgnoreCase("EXIT") -> {
            System.out.println("Exiting now.");
        }
        default -> System.out.println("Please retry.");
    }
}

With Java 21’s new features, your code becomes more concise and easier to read, making pattern matching a powerful tool in Java programming toolkit.

Benefits of Java 21

  1. Improved code readability
  2. Reduced boilerplate code
  3. Simplified complex switch statements
  4. Enhanced developer productivity

In conclusion, Java 21 Pattern Matching for Switch is a valuable addition to the Java language, making code selection more straightforward and efficient. By using patterns in switch statements, developers can write cleaner, more concise, and more readable code, ultimately improving software quality and maintainability.

For additional information on pattern matching in Java, you can visit the following link: Pattern Matching (JEP 441) – OpenJDK. This link provides detailed information about the Java Enhancement Proposal (JEP) for pattern matching in Java.

Java 21 Unnamed Patterns and Variables with Examples

Java 21 Unnamed Patterns and Variables is introduced as a preview feature JEP-443 that simplifies data processing. It enables the use of unnamed patterns and variables, denoted by an underscore character (_), to match components within data structures without specifying their names or types. Additionally, you can create variables that are initialized but remain unused in the code.

Let’s break this down in simpler terms:

Introduction:

Before we dive into the world of Java records, let’s consider a situation where the conciseness of code
presents a challenge. In this instance, we will work with two record types: “Team” and “Member.”

record ProjectInfo(Long projectID, String projectName, Boolean isCompleted) {
  // Constructor and methods (if any)
}

record TeamMemberInfo(Long memberID, String memberName, LocalDate joinDate, Boolean isActive, ProjectInfo projectInfo) {
  // Constructor and methods (if any)
}

In Java, records provide a streamlined approach to create immutable data structures, particularly suitable for storing plain data. They eliminate the need for traditional getter and setter methods.

Now, let’s delve into how record patterns can simplify code by deconstructing instances of these records into their constituent components.

TeamMemberInfo teamMember = new TeamMemberInfo(101L, "Alice", LocalDate.of(1985, 8, 22), true, projectInfo);

if (teamMember instanceof TeamMemberInfo(Long id, String name, LocalDate joinDate, Boolean isActive, ProjectInfo projInfo)) {
  System.out.printf("Team member %d joined on %s.", id, joinDate); 
  //Team member 101 joined on 1985-8-22
}

When working with record patterns, it’s often the case that we require only certain parts of the record and not all of them.
In above example, we exclusively used the “id” and “joinDate” components.
The presence of other components such as “name,” “isActive,” and “projInfo” doesn’t enhance clarity; instead, they add brevity without improving readability.

In Java 21, this new feature is designed to eliminate this brevity.

Exploring Unnamed Patterns and Variables

In Java 21, a new feature introduces the use of underscores (_) to represent record components and local variables, indicating our lack of interest in them.

With this new feature, we can revise the previous example more concisely as shown below.
It’s important to observe that we’ve substituted the “name,” “isActive,” and “projInfo” components with underscores (_).

if (teamMember instanceof TeamMemberInfo(Long id, _, LocalDate joinDate, _, _)) {
  System.out.printf("Team member %d joined on %s.", id, joinDate); //Team member 101 joined on 1985-8-22
}

In a similar manner, we can employ the underscore character with nested records when working with the TeamMemberInfo record,
especially when we don’t need to use certain components. For example, consider the following scenario where we only
require the team member’s ID for specific database operations, and the other components are unnecessary.

if (teamMember instanceof TeamMemberInfo(Long id, _, _, _, _)) {
  // Utilize the team member's ID
  System.out.println("Team Member ID is: " + id);  //Team Member ID is: 101
}

In this code, the underscore (_) serves as a placeholder for the components we don’t need to access
within the TeamMemberInfo record.

Starting from Java 21, you can use unnamed variables in these situations:

  1. Within a local variable declaration statement in a code block.
  2. In the resource specification of a ‘try-with-resources’ statement.
  3. In the header of a basic ‘for’ statement.
  4. In the header of an ‘enhanced for loop.’
  5. As an exception parameter within a ‘catch’ block.
  6. As a formal parameter within a lambda expression.

Java 21 Unnamed Patterns and Variables Practical Examples

Let’s dive into a few practical examples to gain a deeper understanding.

Example 1: Local Unnamed Variable

Here, we create a local unnamed variable to handle a situation where we don’t require the result.

int _ = someFunction(); // We don't need the result

Example 2: Unnamed Variable in a ‘catch’ Block

In this case, we use an unnamed variable within a ‘catch’ block to handle exceptions without utilizing the caught value.

String userInput = "Your input goes here"; 

try { 
    int number = Integer.parseInt(userInput);
    // Use 'number'
} catch (NumberFormatException _) { 
    System.out.println("Invalid input: " + userInput);
}

Example 3: Unnamed Variable in a ‘for’ Loop

In the following example, we employ an unnamed variable within a simple ‘for’ loop, where the result of the runOnce() function is unused.

for (int i = 0, _ = runOnce(); i < array.length; i++) {
  // ... code that utilizes 'i' ...
}

Example 4: Unnamed Variable in Lambda Expression

// Define a lambda expression with an unnamed parameter
 Consumer<String> printMessage = (_) -> {
 		System.out.println("Hello, " + _);
 };

// Use the lambda expression with an unnamed parameter
printMessage.accept("John"); //Hello, John

Example 5: Unnamed Variable in try-with-resources

try (var _ = DatabaseConnection.openConnection()) {
    ... no use of the established database connection ...
}

In all the above examples, where the variables remain unused and their names are irrelevant, we simply declare them without providing a name, using the underscore (_) as a placeholder. This practice enhances code clarity and reduces unnecessary distractions.

Conclusion
Java 21 introduces a convenient feature where you can use underscores (_) as placeholders for unnamed variables. This simplifies your code by clearly indicating that certain variables are intentionally unused within their specific contexts. You can apply unnamed variables in multiple situations, such as local variable declarations, ‘try-with-resources’ statements, ‘for’ loop headers, ‘enhanced for’ loops, ‘catch’ block parameters, and lambda expressions. This addition to Java 21 improves code readability and helps reduce unnecessary clutter when you need to declare variables but don’t actually use them in your code.

Java 21 Unnamed Classes and Instance Main Methods

Java is evolving to make it easier for beginners to start coding without the complexity of large-scale programming. With the introduction of Unnamed Classes in Java 21, this enhancement allows students to write simple programs initially and gradually incorporate more advanced features as they gain experience. This feature aims to simplify the learning curve for newcomers.

Simplifying a Basic Java Program

Think about a straightforward Java program, like one that calculates the sum of two numbers:

public class AddNumbers { 

    public static void main(String[] args) { 
        int num1 = 5;
        int num2 = 7;
        int sum = num1 + num2;
        System.out.println("The sum is: " + sum); //12
    }
}

This program may appear more complicated than it needs to be for such a simple task. Here’s why:

  • The class declaration and the mandatory public access modifier are typically used for larger programs but are unnecessary here.
  • The String[] args parameter is designed for interacting with external components, like the operating system’s shell. However, in this basic program, it serves no purpose and can confuse beginners.
  • The use of the static modifier is part of Java’s advanced class-and-object model. For beginners, it can be perplexing. To add more functionality to this program, students must either declare everything as static (which is unconventional) or Learn about static and instance members and how objects are created.

In Java 21, making it easier for beginners to write their very first programs without the need to understand complex features designed for larger applications. This enhancement involves two key changes

1. Instance Main Methods:

The way Java programs are launched is changing, allowing the use of instance main methods. These methods don’t need to be static, public, or have a String[] parameter. This modification enables simplifying the traditional “Hello, World!” program to something like this:

class GreetingProgram {
    void greet() {
        System.out.println("Hello, World!");
    }
}

Execute the program using the following command: java --source 21 --enable-preview GreetingProgram.java

Output

Hello, World!

2. Unnamed Classes:

Java programmers introducing unnamed classes to eliminate the need for explicit class declarations, making the code cleaner and more straightforward:

void main() {
    System.out.println("Welcome to Java 21 Features");
}

Save this file with a name of your choice, then run the program using the following command: java --source 21 --enable-preview YourFileName.java

Ensure that you replace “YourFileName” with the actual name of your file.

Output:

Welcome to Java 21 Features

Please find the reference output below.

Java 21 Unnamed Classes and Instance Main Methods

Please note that these changes are part of a preview language feature, and they are disabled by default. To try them out in JDK 21, you can enable preview features using the following commands:

Bash
Compile the program with: javac --release 21 --enable-preview FileName.java

run it with: java --source 21 --enable-preview FileName.java

You can find more information about these features on the official OpenJDK website by visiting the following link: Java Enhancement Proposal 443 (JEP 443).

To sum it up, Java 21 is bringing some fantastic improvements to make programming more beginner-friendly and code cleaner. With the introduction of instance main methods and unnamed classes, Java becomes more accessible while maintaining its strength. These changes mark an exciting milestone in Java’s evolution, making it easier for newcomers to dive into coding. Since these features are in preview, developers have the opportunity to explore and influence the future of Java programming.

Java String Templates in Java 21: Practical Examples

What exactly is a String Template?

Template strings, often known as Java string templates, are a common feature found in many programming languages, including TypeScript template strings and Angular interpolation. Essentially, they allow us to insert variables into a string, and the variable values are determined at runtime. As a result, Java string templates generate varying output based on the specific values of the variables.

Here are examples of Java string templates with different greetings and names:

// TypeScript
const nameTS = "John";
const messageTS = `Welcome ${nameTS}!`;

// Angular
const nameAngular = "Jane";
const messageAngular = `Welcome {{ ${nameAngular} }}!`;

// Python
namePython = "Alice"
messagePython = "Welcome {namePython}!"

// Java (using String.format)
String nameJava = "Bob";
String messageJava = String.format("Welcome %s", nameJava);

Each of the examples provided above will give you the same result when used with the same ‘name’ variable. JEP-430 is an effort to introduce template string support in the Java programming language, much like what you see here:

In Java 21, you can create a message using this syntax:

String name = "Bob";
String message = STR."Welcome  \{name}!";

String Templates in the Java Language

The Old-Fashioned Way

String formatting in Java is not a new concept. Historically, programmers have employed various methods to create formatted strings, including string concatenation, StringBuilder, String.format(), and the MessageFormat class.

public class WelcomeMessage {

    public static void main(String[] args) {
        String message;
        String name = "John";

        // Concatenate a welcome message
        message = "Welcome " + name + "!";

        // Use String.format for formatting
        message = String.format("Welcome %s!", name);

        // Format using MessageFormat
        message = new MessageFormat("Welcome {0}!").format(new Object[] { name });

        // Construct efficiently with StringBuilder
        message = new StringBuilder().append("Welcome ").append(name).append("!").toString();

        // Display the final welcome message
        System.out.println(message);
    }
}

the output for each method will be the same, and it will display:

Welcome John!

Java String Templates in Java 21: Secure Interpolation

Java 21 has introduced template expressions, drawing inspiration from other programming languages. These expressions enable dynamic string interpolation during runtime. What sets Java’s approach apart is its focus on minimizing security risks, particularly when handling string values within SQL statements, XML nodes, and similar scenarios.

In terms of syntax, a template expression resembles a regular string literal with a specific prefix:

// Code Example
String message = STR."Greetings \{name}!";

In this context:

  • STR represents the template processor.
  • There is a dot operator (.) connecting the processor and the expression.
  • The template string contains an embedded expression in the form of {name}.
  • The outcome of the template processor, and consequently the result of evaluating the template expression, is often a String—although this isn’t always the case.

Template Processors in Java 21

In the world of Java, you’ll encounter three distinct template processors:

STR: This processor takes care of standard interpolation, making it a versatile choice for string manipulation.

FMT: Unlike its counterparts, FMT not only performs interpolation but also excels at interpreting format specifiers located to the left of embedded expressions. These format specifiers are well-documented within Java’s Formatter class.

RAW: RAW stands as a steadfast template processor, primarily generating unprocessed StringTemplate objects.

Here’s an example demonstrating how each of these processors can be utilized:

Here’s an example demonstrating how each of these processors can be utilized:

import static java.lang.StringTemplate.STR;
import static java.lang.StringTemplate.RAW;

public class TemplateProcessorTest {
    public static void main(String[] args) {
        String name = "JavaDZone";

        System.out.println(STR."Welcome to \{name}");
        System.out.println(RAW."Welcome to \{name}.");
    }
}

To put it into action, execute the following command within your terminal or command prompt:

Bash
java --enable-preview --source 21 TemplateProcessorTest.java

Be sure to substitute “TemplateProcessorTest.java” with the actual name of your Java class.

Performing Arithmetic Operations within Expressions

In Java 21, you have the capability to carry out arithmetic operations within expressions, providing you with the means to compute values and showcase the results directly within the expression itself.

For instance, consider the following code snippet:

int operand1 = 10, operand2 = 20;

String resultMessage = STR."\{operand1} + \{operand2} = \{operand1 + operand2}";  // This will result in "10 + 20 = 30"

You can use multi-line expressions:

For the sake of improving code readability, you can split an embedded expression into multiple lines, emulating the style often seen in nested method calls resembling a builder pattern.

Here’s an example to illustrate this:

System.out.println(STR."The current date is: \{
    DateTimeFormatter.ofPattern("yyyy-MM-dd")
        .format(LocalDateTime.now())
}");

Exploring String Templates in Java 21

The following Java class, StringTemplateTest, serves as an illustrative example of utilizing string templates with the STR template processor. It demonstrates how to integrate string interpolation and various expressions within template strings. Each section is accompanied by a description to provide a clear understanding of the usage.

import static java.lang.StringTemplate.STR;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.LocalTime;

public class StringTemplateTest {

  private static String name = "JavaDZone";
  private String course = "Java21 Features";
  private static int a = 100;
  private static int b = 200;

  public static void main(String[] args) {
  
      // Using variable in template expression.
      System.out.println(STR."Welcome to \{name}");

       // Utilizing a method in the template expression.
      System.out.println(STR."Welcome to \{getName()}");

      
      StringTemplateTest st = new StringTemplateTest();

     // Using non-static variable in the template expression.
      System.out.println(STR."Welcome to \{st.course}");

       // Performing arithmetic operations within the expression.
      System.out.println(STR."\{a} + \{b} = \{a+b}");

        // Displaying the current date using expression
      System.out.println(STR."The current date is: \{DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDateTime.now())}");
      
      }


  public static String getName() {
    return name;
  }
  
}

To put it into action, execute the following command within your terminal or command prompt:

Bash
java --enable-preview --source 21 StringTemplateTest .java

make sure to change “StringTemplateTest.java” with the actual name of your Java class.

Java String Templates

If you attempt to run or compile the StringTemplateTest class using the traditional java or javac methods, you may encounter the following error:

Java String Templates in java 21

This error message indicates that string templates are considered a preview feature in Java, and they are not enabled by default. To enable and utilize string templates in your code, you should use the --enable-preview –source 21 flag when running or compiling your Java program. This flag allows you to take advantage of string templates’ functionality.

In summary, this Java tutorial has explored the concept of string templates in Java. This feature was introduced in Java 21 as a preview, offering a fresh addition to the language’s capabilities. To stay updated on potential improvements and enhancements to this feature, be sure to keep an eye on the Java release notes. Enjoy your learning journey!

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!