BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News Java Gets a Boost with the Record Pattern, Enabling More Expressive Coding

Java Gets a Boost with the Record Pattern, Enabling More Expressive Coding

This item in japanese

Bookmarks

JEP 440, Record Patterns, has been promoted from Proposed to Target to Targeted status for JDK 21. This JEP finalizes this feature and incorporates enhancements in response to feedback from the previous two rounds of preview: JEP 432, Record Patterns (Second Preview), delivered in JDK 20; and JEP 405, Record Patterns (Preview), delivered in JDK 19. This feature enhances the language with record patterns to deconstruct record values.

Record patterns may be used in conjunction with type patterns to "enable a powerful, declarative, and composable form of data navigation and processing." Type patterns were recently extended for use in switch case labels via: JEP 420, Pattern Matching for switch (Second Preview), delivered in JDK 18, and JEP 406, Pattern Matching for switch (Preview), delivered in JDK 17. The most significant change from JEP 432 removed support for record patterns appearing in the header of an enhanced for statement.

With all these changes, Java is now on a path towards a more declarative, data-focused programming style with the introduction of nestable record patterns. This evolution comes after the successful integration of pattern matching with the instanceof operator introduced in Java 16 with JEP 394.

Consider a situation where you have a record, Point, and an enum, Color:

record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }

The new record pattern allows testing whether an object is an instance of a record, and directly deconstructing its components. For instance:

if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
    System.out.println(ul.c());
}

Even more powerful is the ability to use nested patterns, which allow further decomposition of the record value. Consider the following declaration:

record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}

If we want to extract the color from the upper-left point, we could write:

if (r instanceof Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr)) {
    System.out.println(c);
}

This evolution of record patterns extends pattern matching to deconstruct instances of record classes, thus enabling more sophisticated data queries. It allows for testing if an object is an instance of a record and directly extracting the components of the object. This approach makes the code more concise and less error-prone. Consider the following example:

static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) {
    if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c),
                               var lr)) {
        System.out.println("Upper-left corner: " + x);
    }
}

In addition, the introduction of nested patterns takes this further by providing the ability to destructure nested data structures. They give developers the power to centralize error handling since either the entire pattern matches or not. This eliminates the need for checking and handling each individual subpattern matching failure.

These nested patterns also play nicely with the switch expressions introduced by JEP 441. Pattern matching for switch expressions augments the switch statement to allow the use of patterns in case labels. This leads to more expressive code and reduces the chances of bugs due to missed cases in switch statements.

For example, consider the declarations:

class A {}
class B extends A {}
sealed interface I permits C, D {}
final class C implements I {}
final class D implements I {}
record Pair<T>(T x, T y) {}

Pair<I> p;

With the record pattern and exhaustive switch, we can do the following:

switch (p) {
    case Pair<I>(C c, I i) -> ...
    case Pair<I>(D d, C c) -> ...
    case Pair<I>(D d1, D d2) -> ...
}

However, these updates come with some risks and assumptions. As with any language change, there's a risk of impacting the existing codebase. Additionally, these changes assume that developers are familiar with record classes and pattern matching, two relatively new features to Java.

Looking ahead, there are many directions in which the record patterns could be extended. These include varargs patterns for records of variable arity, unnamed patterns that match any value but do not declare pattern variables, and patterns that can apply to values of arbitrary classes rather than only record classes.

In conclusion, introducing record and nested patterns in Java is a significant leap forward for the language. It allows for a more declarative coding style, which can lead to cleaner, more understandable code. While some risks are involved, the potential benefits make this a promising feature for future versions of Java.

About the Author

Rate this Article

Adoption
Style

BT