DEV Community

Cover image for Exploring Java Record Patterns and Pattern Matching
MyExamCloud
MyExamCloud

Posted on

Exploring Java Record Patterns and Pattern Matching

Java record patterns were introduced in Java 16 as part of Project Amber and are enhanced in Java 21 with the addition of deconstructors and pattern matching. This feature allows for a more concise and flexible way of handling data in Java applications. In this tutorial, we will cover the concept of record patterns, how they help in pattern matching, and various coding examples.

1. Quick Recap of Java Records
Before diving into record patterns, let's do a quick recap of Java records. Records were introduced in Java 14 as a new type of class specifically designed for holding immutable data. They eliminate the need for writing boilerplate code by providing a more concise syntax to define classes with data. In the following example, we define a record named "Person" with three fields: first name, last name, and age.

record Person(String firstName, String lastName, int age) {}

Records, being immutable, restrict certain characteristics:

  • All fields are final
  • The class is implicitly final
  • Declaration of instance fields is prohibited

Records automatically generate a constructor and getter methods for each field, making them more suitable for data carriers than traditional classes.

2. What are Record Patterns?
Record patterns, introduced in Java 21, combine the concept of patterns with records that allows for deconstructing and extracting values from records easily. A record pattern consists of a record class type and optional pattern variables that are assigned values extracted from the target record only if the test is successful.

The null value does not match any record pattern, so the pattern variables will not be initialized in this case.

3. Record Destructors
A destructor is the inverse of a constructor. It extracts values from an object instead of adding them. In the context of records, a destructor is called a "deconstructor." It allows for decomposing record values into their individual components. Suppose we have a record named "Name" with the following fields:

record Customer(String firstName, String lastName, int age) {}

We can use a deconstructor to extract the values of these fields from an instance of the "Customer" record, as shown in the following example:

Customer customer = new Customer("Jane", "Smith", 30);

if (customer instanceof Customer(String firstName, String lastName, int age)) {
System.out.println("First Name: " + firstName); // Jane
System.out.println("Last Name: " + lastName); // Smith
System.out.println("Age: " + age); // 30
}

Record structures can be useful in situations where we need to store and retrieve multiple related values as a single object. The deconstruction feature of records allows us to easily extract and use those values in our code.

4. Record Patterns in Switch Statements
Java 16 introduced pattern matching for instanceof statements, which was further expanded in Java 21 to include switch statements. It allows for more concise and readable code while handling multiple patterns.

Let's say we have a record named "Account " with three subtypes: "SavingAccount ," "CreditCardAccount," and "HomeLoanAccount." Each subtype has a different way of calculating its balance. We can use the record pattern in a switch statement to handle these three cases, as shown below:

interface Account {}
record SavingAccount (double balance) implements Account {}
record CreditCardAccount(double creditLimit, double used) implements Account {}
record HomeLoanAccount(double totalAmount, double amountPaid) implements Account {}

Account account= new CreditCardAccount(10000, 1000);

switch (shape) {
case SavingAccount s:
System.out.println("Account Balence is " + balance);
break;
case CreditCardAccount c:
System.out.println("Credit Balence is: " + (creditLimit-used));
break;
case HomeLoanAccount h:
System.out.println("Balence " +(totalAmount-amountPaid));
break;
default:
System.out.println("Unknown Account");
}

5. Nested Patterns
In addition to extracting values from a single record, record patterns can also be nested to handle more complex records. Let's say we have a record named "Account" that has a field of type "Customer" and another field of type "Address." We can use nested record patterns to extract the values from both of these fields, as shown in the following example:

record Customer(String firstName, String lastName, int age) {}

record Account(Customer cust, Address address) {}

Customer customer = new Customer("John", "Doe", 25);
Address address = new Address("123 Main St.","City", "State");
Account account = new Account(customer, address);

if (account instanceof Account(Customer(fName, lName, age), Address(street, city, state))) {
System.out.println("First Name: " + fName); // John
System.out.println("Last Name: " + lName); // Doe
System.out.println("Age: " + age); // 25
System.out.println("Address: " + street + ", " + city + ", " + state); // 123 Main St., City, State
}

In this example, we use two nested patterns to extract the values from the "Customer" and "Address" fields of the "Account" record.

6. Component Matching
Record patterns allow for more flexibility as pattern variables do not have to match the component names of a record. As long as they are compatible, the pattern matching will be successful. For example:

record Customer(String firstName, String lastName, int age) {}

record Account(Customer cust, Address address) {}

Customer customer = new Customer("John", "Doe", 25);

if (customer instanceof Customer(var fn, var ln, var age)) {
System.out.println("First Name: " + fn); // John
System.out.println("Last Name: " + ln); // Doe
System.out.println("Age: " + age); // 25
}

// Accessing nested record components
Account account = new Account(customer, new Address("123 Main St", "New York", "NY"));
if (account instanceof Account(var cust, var address)) {
System.out.println("Customer: " + cust); // Customer[firstName=John, lastName=Doe, age=25]
System.out.println("Address: " + address); // Address[street=123 Main St, city=New York, state=NY]
}

Here, the pattern variables "fn" and "ln" are different from the corresponding component names of "fName" and "lName" in the "Customer" record. This makes record patterns more flexible and avoids any potential conflicts with variable names.

7. Conclusion
In conclusion, Java record patterns provide a convenient way to deconstruct records and extract values from them. They make code more concise, clean, and readable while handling pattern matching in instanceof and switch statements. Record patterns, combined with record destructors, allow for more robust handling of complex records. Overall, this feature enhances the use of records as data carriers and makes Java programming more pleasant.

Developers can discover more Java 21 features by studying for the SE 21 Developer Certified Professional exam with the use of MyExamCloud's 1Z0-830 Practice Tests.

Top comments (0)