Hero Illustration
Java, programming language

New Useful Features for Java 21

According to the Tiobe Index, Java is the 4th most popular programming language. It continues to evolve with each release, bringing in new features, improvements, and optimizations.

Many changes and improvements have been made to this technology over the years. Since Java version 9, the language has been updated every 6 months, and the latest version (Java 21 or JDK 21) was released on September 19th, 2023.

In this blog post, we will dive into some of the useful features introduced in Java 21 and compare them with the older versions.

  • Pattern Matching for Switch.
  • Sequenced Collection.

Pattern Matching for Switch

Pattern matching for switch was first unveiled in Java 17 (JEP 406) and has been progressively enhanced in subsequent versions of the JDK (JEP 406, 420, 427, 433). By the time Java 21 was rolled out, it had matured into a complete, non preview feature.

What is Pattern Matching in Java?
Since the 1960s, pattern matching has evolved across multiple programming paradigms, establishing itself as a foundational technique in diverse programming languages. This technique has been integrated into functional languages like Haskell and ML. Over time, it has seamlessly transitioned into the realm of object-oriented languages, with recent adoptions in Scala and C#, showcasing its versatility and enduring relevance.

A pattern consists of a match predicate, which assesses whether the pattern aligns with a given target and a series of pattern variables that are conditionally extracted upon a successful match. This versatile construct allows for the simplification of language features that evaluate an input, such as instance of and switch, enabling them to support patterns that are directly matched against the input.

A variant of pattern matching we are familiar with is a type of pattern matching by using the instanceof keyword:

Or if we are going to have a more complex logic inside that, it will resemble this:

While the ‘instanceof’ operator simplifies some of the code, it is still relatively poor and needs a cleaner fix. However, if you look at it with more detail, it has similarities to switch construction, with several different “cases” that must be handled. So, instead of utilizing the “instanceof” in further if-else statements, why not use a more appropriate language feature?

What Does Pattern Matching for Switch Look Like?

Looking at the approaches with switches taken so far, pattern matching is based solely on the equality of their constant case variants. The resulting code is easier to write compared to if-else multi-cases which are quite disturbing to look at, but the available conditions are limited or only available for constant type.

The next logical evolution is to allow patterns to match values in a switch, not just constants. Thinking about it further, you might want a comprehensive expression if the condition is used. 

However, because Java is known for its smaller, incremental, and the most important approach, known as Backward Compatibility approach, rather than giving all features together in one feature approach, Java introduced pattern matching. 

This is how your code would look like when using a matching pattern with switch:

As shown in the example above, we can now use another technique to write various conditions. And when compared to if-else, it is more readable and concise.

Why is it Useful?
Let’s use one example.

Imagine that we have a list of employees that belong to various grade levels such as Analyst Programmer or Programmer, and each employee has a different number of skills. The requirement is to group the employees based on certain criterias.

These code snippets aim to filter a list of employees based on their job roles and specific criterias.

Employing a combination of conditional checks and type-based filtering, it categorizes employees as Programmer Employees or Analyst Programmer Employees, storing the filtered results in separate lists.

Let’s break down this process:

  1. Initialize three lists to store filtered employees: filteredPG for Programmer Employees, filteredAP for Analyst Programmer Employees, and a map filteredEmployeesMap to categorize employees by their job roles.
  2. Iterate over each employee in the employees list.
  3. For Programmer Employees:
    • Verify if the employee is an instance of ProgrammerEmployee.
    • Check if the employee’s skills include “Flutter”.
    • Ensure that the employee’s note contains the phrase “the analysis skill equals AP”.
    • If all conditions are met, add the employee to the filteredPG list.
  4. For Analyst Programmer Employees:
    • Verify if the employee is an instance of AnalystProgrammerEmployee.
    • Check if the employee’s skills include both “Java” and “Flutter”.
    • If the conditions are met, add the employee to the filteredAP list.
  5. After processing all employees, create a mapping of job roles to filtered lists:
    • Put the filtered Analyst Programmer Employees list (filteredAP) into the map with the key “AP”.
    • Put the filtered Programmer Employees list (filteredPG) into the map with the key “PG”.
  6. Return the filteredEmployeesMap containing categorized employees based on their job roles.

This is what we achieve with the previous versions of Java:

Upon looking at the picture above, we can see that we need to do some checking before they are grouped.

1. We are required to have null value checking so that we can handle Null Pointer Exception

2. We need to check whether the employee object is an instance of the correct class or not.

3. We need to cast the employee object in order to get the actual value and run some checks to meet the rule.

4. The code is quite hard to read. 

From my point of view, the code above is not effective and clean because there are still some processes that we must do. In addition, the code will be more complex if the requirement or rule gets bigger. As a result, this makes it harder to read and maintain.

Let’s do it with a different technique. This time, by using Pattern Matching for Switch in Java 21.

This is how we can achieve it with the latest version of Java:

As we can see in the example above, there are a lot of improvements that are useful for us as Java Developers.

  1. Instead of having null checking, we can either handle null manually, or we can put it as the default case.

Also, if we don’t add a default case, there will be a prompt or compilation error shown.

This will make the code safer and more useful, right?

  1. We don’t need to check the object instance by using instanceof, as this will be done automatically.
  2. We don’t need to do casting to get the value of the object.
  3. And the most important thing is the Guard Clause, that is introduced in this new version:

This removes the need for us to have if-else conditions inside the case block. 

In summary, the code is much simpler and easier to read.

What are Sequenced Collections in Java?

With the release of Java 21, the Collection framework has been enriched by the introduction of the “Sequenced Collections” feature. This enhancement brings new default methods to the existing collection classes and interfaces, enabling direct access to the first and last elements of a collection.

A Sequenced Collection contains the first and last elements, with the elements between them having successors and predecessors. This feature is basically an improvement of the “Java Collection” feature, which was submitted through the JEP 431 proposal.

Sequenced Collection introduced 3 new Interfaces in the Java Collection:
1. SequencedCollection
2. SequencedSet
3. SequencedMap

In this blog, I’ll talk about SequencedCollection only.

Why is Sequenced Collection Useful?

The problem with Java Collection before Java 21 was released was that it did not have a feature to perform operations at the start and end of a collection.

One that has this feature is Deque, however, the others don’t have that feature, so the operations at the beginning and end are a little tricky to do.

Collection ClassFirst ElementLast Element
Listlist/get(0)list.get(list.size() – 1)
Sorted ListsortedList.first()sortedList.last()
LinkedHashSetlinkedHashSet.iterator().next()// missing

As shown in the table, each collection has a different method of accessing the first data or the last data and there is no method to put an element to be the first or the last. Meaning, there is no standard.

Therefore, Sequenced Collection has been introduced to make a new standard for getting the first or last data for all classes derived from Collection class.

As you can see, all methods except the ‘reversed()’ method are default methods and provide a default implementation. This means that existing collection classes such as ArrayList class and LinkedList class can implement this interface without changing their code.

Let’s look at a typical use case.

Imagine you’re developing a project management tool for a software development team. The tool allows users to manage the priority level from lowest to highest.

The goal is to move a specific task to the highest priority position within a list of tasks. So, the plan will be:

  1. Take a list of tasks and the ones that need to be made the highest priority.
  2. Find the task by name.
  3. Remove it from its current position on the list.
  4. Add it to the beginning of the list, effectively making it the highest priority task.
  5. Finally, return the updated list of tasks.

How this is achieved through the old versions of Java:

1. Let’s create a method to achieve this goal.

Finding the Task by Name: 
The method makeHighestPriorityOldVersion takes a list of tasks (tasks) and the name of a task (taskName) that needs to be moved to the highest priority position. It iterates over the list of tasks to find the index of the task with the specified name. If the task is found, its index is stored. 

Moving the Task to the Highest Priority
If the task with the specified name is found (index != -1), the method removes it from its current position in the list using the remove method. Then, it adds the task back to the list at the beginning (add(0, task)), effectively making it the highest priority task. 

Returning the Updated List: 
The method returns the updated list of tasks, whether the specified task was found and moved or not. 

2. Let’s get the result!

As you can see, that task has been put on the highest priority within the list.

So, how can we achieve it with the recent version of Java?

Let’s see the difference and how the latest version makes our code look simpler.

Here’s your result:

As you can see from the code, we just need to add two lines of code to achieve the goal.

Of course, it will depend on the requirements, but the sequenced collection will give us options that might be useful for getting a solution.

Pattern Matching for Switch and Sequenced Collection are a valuable addition to Java 21, enhancing the language’s ease of use. These features make coding in Java 21 even more readable, accessible, and efficient. 

Enjoy the improvement in writing Java Code and Happy Coding!

Contact us to learn more!

Please complete the brief information below and we will follow up shortly.

    ** All fields are required
    Leave a comment