Pattern Matching in Java 21

Pattern Matching in Java 21

Java has evolved significantly over the years, continuously improving its syntax to reduce boilerplate and make code more expressive. Standardized in Java 17 and improved in Java 21, one of the standout features introduced in recent versions is pattern matching, which simplifies type checks and conditional logic while enhancing code readability.

In this article, we’ll explore how pattern matching works in Java, how it has evolved, and why it’s a powerful tool for modern development.


What Is Pattern Matching in Java?

Pattern matching allows checking the type of an object and automatically bind it to a variable if the test succeeds, eliminating the need for explicit casting. Initially introduced in Java 16 for instanceof checks, it has since been extended to switch expressions and statements, making code even more concise.

Traditionally, type checking in Java required explicit casting:

Object obj = "Hello, World!";
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.toUpperCase()); // HELLO, WORLD!
}        

With pattern matching, Java simplifies this by eliminating redundant casting:

if (obj instanceof String s) {
    System.out.println(s.toUpperCase()); // HELLO, WORLD!
}        

Here, 's' is automatically assigned if obj 'is' a String, reducing the code and improving readability.

Additionally, it can be combined with additional conditions for further refinement.

if (obj instanceof String s && s.length() > 5) {
    System.out.println(s.substring(0, 5)); // Output: Hello
}        

Flow Scoping in Pattern Matching

One of the biggest advantages of pattern matching is flow scoping, the ability to restrict variable usage to areas where the type check is valid.

Object obj = "Java";
if (obj instanceof String s) {
    System.out.println(s.toLowerCase()); // Accessible here
}
// System.out.println(s); // Compilation error: 's' is out of scope        

This guarantees that the pattern variable is only accessible in the scope where its type is known to be valid.


Switch Expressions vs Switch Statements

Before exploring pattern matching with switch, it’s important to understand the key changes introduced in Java 17, which made switch more concise and expressive.

Prior to Java 17, switch statements required explicit break statements to prevent fallthrough between cases. The syntax was also more verbose due to the use of colons (:).

/* 
If break is omitted, execution will continue to the next case until a break or return is encountered. Without breaks, for example, the input 1 would output all seasons. 
*/

public static void printSeason(int month) {
    switch (month) {
        case 12, 1, 2:
            System.out.println("Winter");
            break;
        case 3, 4, 5:
            System.out.println("Spring");
            break;
        case 6, 7, 8:
            System.out.println("Summer");
            break;
        case 9, 10, 11:
            System.out.println("Autumn");
            break;
        default:
            System.out.println("Invalid month");
    }
}        

With switch expressions (Java 17+), you can eliminate break statements, directly return values, and simplify the syntax. Here’s how the same method can be rewritten:

public static void printSeason(int month) {
    String season = switch (month) {
        case 12, 1, 2 -> "Winter";
        case 3, 4, 5 -> "Spring";
        case 6, 7, 8 -> "Summer";
        case 9, 10, 11 -> "Autumn";
        default -> "Invalid month";
    };

    System.out.println(season);
}        

For multi-statement cases, you can combine code blocks with yield to return values:

public static void printSeason(int month) {
    String season = switch (month) {
        case 12, 1, 2 -> {
            System.out.println("It's cold outside!");
            yield "Winter";
        }
        case 3, 4, 5 -> {
            System.out.println("Flowers are blooming!");
            yield "Spring";
        }
        case 6, 7, 8 -> {
            System.out.println("Time for vacations!");
            yield "Summer";
        }
        case 9, 10, 11 -> {
            System.out.println("Leaves are falling!");
            yield "Autumn";
        }
        default -> {
            System.out.println("Invalid input detected.");
            yield "Invalid month";
        }
    };

    System.out.println(season);
}        

Pattern Matching in switch Statements

Traditionally, prior to Java 17, to get switch statements to work with different types dinamically, you would need to write them with type checking and explicit casting, resulting in some questionable code quality-wise.

static String process(Object obj) {
    switch (obj.getClass().getSimpleName()) {
        case "String":
            String s = (String) obj; // Explicit cast required
            if (s.length() > 5) {
                return "Long string: " + s;
            } else {
                return "Short string: " + s;
            }
        case "Integer":
            return "Integer: " + obj; // No casting needed (toString() works)
        default:
            return "Unknown type";
    }
}        

From Java 17 on, this can be a bit improved with switch expressions, but still requiring type checking and explicit casting:

static String process(Object obj) {
    return switch (obj.getClass().getSimpleName()) {
        case "String" -> {
            String s = (String) obj;  // Explicit cast required
            yield s.length() > 5 ? "Long string: " + s : "Short string: " + s;
        }
        case "Integer" -> "Integer: " + obj; // No need to cast for `toString()`
        default -> "Unknown type";
    };
}        

With Java 21, you can now use pattern matching inside switch expressions and reduce a lot boilerplate and potentially unsafe code.

static String process(Object obj) {
    return switch (obj) {
        case String s when s.length() > 5 -> "Long string: " + s;
        case String s -> "Short string: " + s;
        case Integer i -> "Integer: " + i;
        default -> "Unknown type";
    };
}        

? The when clause adds additional conditions to refine matches.

? You may have two cases for the same type. In this case, the first case will only be applied to strings with length less than 5, while the second case will serve as a fallback for all other strings, since it is not tied to any other condition. It is important to enforce ordering here, as putting the general case before the specific one would make all strings be executed by it.

? Pattern variables (s, i) are scoped within their respective cases.

? Java enforces exhaustiveness, meaning all possible types must be covered, or a default case must be included.


Handling null in Pattern Matching

From Java 21 on, you are allowed explicit handling of null values inside switch expressions:

Object obj = null;
String result = switch (obj) {
    case null -> "Null value";
    case String s -> "String: " + s;
    default -> "Unknown";
};
System.out.println(result); // Output: Null value        

?? Important: case null cannot appear after default, as default must always be the last case.


Final Thoughts

Pattern matching in Java is one of the most impactful recent enhancements, making code cleaner, safer, and more expressive. While initially introduced for instanceof, its expansion to switch expressions marks a significant step forward, as it contributes to:

?? More Readable and Maintainable Code

Pattern matching removes unnecessary casting and makes conditional logic cleaner and more expressive.

?? Improved Type Safety

Flow scoping guarantees that pattern variables are only accessible in valid contexts, reducing runtime errors.

?? Enhanced switch Expressions

With pattern matching, switch statements become even more powerful, reducing the need for complex type checking and casting.

Lucas Torres

Fullstack .Net Core/Angular

3 周

#share

回复
Shivam Sharma

Experienced Java/Spring Boot Developer | Crafting Scalable Solutions for Today's Challenges

3 周

Very informative

Bruno Vieira

Senior Software Engineer | Java | Springboot | Quarkus | Go (Go lang) | RabbitMQ | Kafka | AWS | GCP | React

4 周

Really good content about Pattern Matching Bruno Monteiro. I really like to use pattern matching with switch-case block for making some specific treatments

Alexandre Germano Souza de Andrade

Senior Software Engineer | Backend-Focused Fullstack Developer | .NET | C# | Angular | React.js | TypeScript | JavaScript | Azure | SQL Server

1 个月

Very helpful Bruno Monteiro, thanks for sharing!

Felipe A. R. Pinto

Full Stack Developer Java | React | AWS

1 个月

Useful tips

要查看或添加评论,请登录

Bruno Monteiro的更多文章

社区洞察

其他会员也浏览了