Core Java

Java 21 Unnamed Patterns and Variables

In the world of Java 21, the introduction of the unnamed patterns and variables feature (JEP-443) endeavors to achieve a harmonious blend of clarity and brevity. This is accomplished through the utilization of an underscore character (_), enhancing code conciseness and readability. Let us delve to understand the Unnamed patterns and variables in Java 21.

1. Introduction

Typically, when dealing with intricate objects, it’s not always necessary to access all the data they encapsulate at all times. The ideal scenario involves extracting only the pertinent information from an object, but in practice, this is seldom the case. More often than not, we find ourselves utilizing only a fraction of the data provided. Instances of this scenario are prevalent throughout Object-Oriented Programming, as exemplified by the Single Responsibility Principle. Java 21 addresses this on a more granular level is the unnamed patterns and variable feature. As this feature is currently in preview, it is essential to activate it deliberately. In Maven, accomplishing this involves adjusting the compiler plugin configuration to incorporate the specified compiler argument.

<compilerArgs>
    <arg>--enable-preview</arg>
</compilerArgs>

2. Unnamed Variables

While a new addition to Java, the concept of unnamed variables has been well-received in languages like Python and Go. Java, being more focused on Object-Oriented Programming, takes the front in this feature.

2.1 Enhanced For Loop

Consider a simple Bakery record.

public record Bakery(String name) {}

We iterate through a collection of bakeries to count them, performing some business logic.

for (var bakery : bakeries) {
    total++;
    if (total > limit) {
        // side effect
    }
}

With the unnamed variable feature, we can avoid using the bakery variable.

for (var _ : bakeries) {
    total++;
    if (total > limit) {
        // side effect
    }
}

2.2 Assignment Statements

Unnamed variables can be used with assignment statements, particularly when we need some return values along with a function’s side effects.

static Bakery removeThreeBakeriesAndReturnFirstRemoved(Queue<Bakery> b) {
    var bakery = b.poll();
    var _ = b.poll();
    var _ = b.poll();
    return bakery;
}

Multiple assignments in the same block can be used for better readability.

2.3 Try-Catch Block

Unnamed catch blocks prove to be most helpful when we want to handle exceptions without needing to know the exception details:

try {
    someInvalidMethod(bakery);
} catch (IllegalStateException _) {
    System.out.println("Got an illegal state exception for: " + bakery.name());
} catch (RuntimeException _) {
    System.out.println("Got a runtime exception!");
}

They also work for multiple exception types in the same catch i.e.

catch (IllegalStateException | NumberFormatException _) { }

2.4 Try-With Resources

The try-with syntax also benefits from unnamed variables, especially when certain objects are obtained but not directly utilized:

static void obtainTransactionAndUpdateBakery(Bakery b) {
    try (var _ = new Transaction()) {
        updateBakery(b);
    }
}

Multiple assignments can be used in try-with-resources as well:

try (var _ = new BackendTransaction(); var _ = new FileInputStream("/some/file"))

2.5 Lambda Parameters

Unnamed variables find utility in lambda parameters, allowing for more flexible code reuse. In cases where lambda parameters are not of interest, the feature comes in handy:

static Map<String, List<Bakery>> getBakeriesByFirstLetter(List<Bakery> b) {
    Map<String, List<Bakery>> map = new HashMap<>();
    b.forEach(bakery ->
        map.computeIfAbsent(bakery.name().substring(0, 1), _ -> new ArrayList<>()).add(bakery)
    );
    return map;
}

3. Unnamed Patterns

Unnamed patterns have been introduced as an enhancement to Record Pattern Matching, addressing the common issue of not needing every field in records during deconstruction. To delve into this topic, let’s first introduce a class named Office:

abstract class Office { }

The office can take the form of remote or hybrid:

class RemoteOffice extends Office { }

class HybridOffice extends Office { }

Finally, let’s extend the Bakery class to support parameterized types and introduce a new field called name:

public record Bakery<T extends Office>(String name, boolean isAvailable, T office) { }

3.1 instanceof

When deconstructing records with patterns, unnamed patterns allow us to conveniently ignore fields we don’t need. Consider the scenario where we want to retrieve the name of a bakery from an object:

static String getObjectsName(Object object) {
    if (object instanceof Bakery(String name, boolean isAvailable, Office office)) {
        return name;
    }
    return "No bakery found.";
}

While functional, this approach can be hard to read, and unnecessary variables are being defined. Let’s see how this looks with unnamed patterns:

static String getObjectsNameWithUnnamedPattern(Object object) {
    if (object instanceof Bakery(_, String name, _)) {
        return name;
    }
    return "No bakery found.";
}

This mechanism also works for simpler instanceof definitions, although it might be less advantageous.

4. Conclusion

In conclusion, the introduction of unnamed patterns in Record Pattern Matching offers a valuable enhancement, addressing the common scenario where not all fields of records are needed during deconstruction. This feature proves particularly useful when dealing with complex objects, allowing developers to succinctly express their intent without introducing unnecessary variables.

By leveraging unnamed patterns, the readability of code is improved, making it clearer which components are essential for a specific operation. The examples explored in the context of instanceof checks demonstrate how this feature streamlines the code, focusing on the pertinent information while ignoring irrelevant details.

As Java continues to evolve, these enhancements contribute to the language’s adaptability and developer-friendly nature. Unnamed patterns provide a more concise and expressive way to work with records, ultimately enhancing the overall coding experience for Java developers.

Yatin

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button