Getty Images


Introduction to pattern matching in Java

Pattern matching in Java helps developers better describe data structures and avoid runtime errors, while making code more readable and easier to maintain. Here's how it works.

Experienced Java developers are committed to continuous improvement. We always seek ways to make our code more readable, reliable and efficient. Java's evolution provides a steady stream of powerful features to support this goal.

In this article, we'll delve into one of the most impactful ways to improve your Java code: pattern matching.

What is pattern matching?

Working with complex data structures, especially abstract layers and complex class hierarchies, often requires verbose and error-prone code. Using abstract types frequently requires one to manually check types, cast to specific classes and handle potential null pointer exceptions. These practices clutter codebases and increase the likelihood of runtime errors.

Pattern matching offers an elegant way to deal with these challenges, improve code quality and reduce errors. Fundamentally, it lets us define patterns that describe data structures and types. We can then match values against these patterns; if a match is found, Java automatically extracts and assigns values as needed.

Pattern matching examples

Pattern matching, introduced to Java 14, via JEP 394, enhances the instanceof operator for simpler, safer and more expressive ways to check an object's type and extract its components. Let's see how pattern matching streamlines the following traditional example:

if (obj instanceof String) {
  String s = (String) obj;
  // use s

With pattern matching, we express this more succinctly:

if (obj instanceof String s) {
  // use s directly, no need to cast

Here, String s is the type pattern. If obj is a String, it's automatically cast and assigned to s. The variable s is scoped to the if block.

JEP 405 extended pattern matching with record patterns. Here's another example of how it is applied:

if (o instanceof User(String name, Integer age)) {
    System.out.println("Name: " + name);
    System.out.println("Age: " + age);

In a future article on advanced pattern matching, we'll delve into nested record patterns, using var for type inference, and how record patterns make code even more concise and elegant.

Pattern matching with switch

Pattern matching also integrates seamlessly with switch expressions (JEP 406, 420 and 427). However, before we dive into pattern matching with switch expressions, let's first learn a bit about the enhancements made in the switch expression in Java.

Enhanced switch expression

One of the most significant changes introduced by switch expressions is their ability to return values. This fundamental shift allows for new ways to use switch constructs in your Java code. Let's illustrate the difference between traditional switch statements and enhanced switch expressions.

Developers often use switch statements primarily to execute blocks of code based on matching cases. Take the following example, which categorizes days of the week into weekdays and weekends:

String dayOfWeek = "Tuesday";

switch (dayOfWeek) {
   case "Monday":
   case "Tuesday":
   case "Wednesday":
   case "Thursday":
   case "Friday":
       System.out.println("It's a weekday");
   case "Saturday":
   case "Sunday":
       System.out.println("It's the weekend!");

This approach gets the job done but can feel a bit verbose and cumbersome.

By contrast, the enhanced switch expression introduced a sleek and more functional twist to this familiar structure. It enables direct value assignment based on the evaluated case, and effectively turns the switch statement into an expression that returns a value.

Here's how that same logic looks when refashioned using an enhanced switch expression:

String dayType = switch (dayOfWeek) {
   case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" -> "Weekday";
   case "Saturday", "Sunday" -> "Weekend";
   default -> "Unknown day";

This version is not only more concise but also significantly improves readability and maintainability.

Extending pattern matching fundamentals

Java's pattern matching capability not only builds upon enhanced switch expressions, it further extends their utility, especially when working with complex data types. The combination of pattern matching and switch expressions exemplifies the Java language's evolution toward more expressive and concise coding patterns.

Let's look at a basic example:

private static String determineShapeType(Object shape) {
    return switch (shape) {
        case Circle c -> "It's a circle with radius: " + c.radius();
        case Rectangle r -> "It's a rectangle. Area: " + r.width() * r.height(); 
        case Triangle t -> "It's a triangle!"; 
        default -> "Unknown shape";

// Assuming you have Circle, Rectangle, and Triangle classes/records defined 

This example showcases the power of pattern matching combined with enhanced switch expressions, allowing for precise and readable conditions within switch cases.

Pattern matching offers a significant improvement over traditional Java techniques to handle different types of complex data structures. Its focus on readability and conciseness makes code easier to understand and maintain.

In a subsequent article about advanced pattern matching, we'll see how to apply this Java feature in even more powerful and sophisticated ways.

A N M Bazlur Rahman is a Java Champion and staff software developer at DNAstack. He is also founder and moderator of the Java User Group in Bangladesh.

Dig Deeper on Core Java APIs and programming techniques

App Architecture
Software Quality
Cloud Computing