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

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 features that are relatively new 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, the introduction of record and nested patterns in Java is a significant leap forward for the language. It allows for a more declarative style of coding, which can lead to cleaner, more understandable code. While there are some risks involved, the potential benefits make this a promising feature for future versions of Java.

About the Author

Rate this Article

Adoption
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Community comments

  • Aborted Polymorphism?

    by Jim Cakalic,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    It's interesting that the only examples the author can provide for needing the record "pattern" involve testing the instanceof a variable for a particular record type and then performing some instance-specific behavior based on the result (yes, in this case it's just a trivial println but more often it is not).

    I feel like I'm pointing out the obvious, that this style of programming will take us back to a place where behavior is no longer collocated with the data on which it operates but is instead distributed through a codebase and all changes require shotgun surgery. Not to mention, adding a subclass to a hierarchy (is there a subrecord of a record?) requires finding all these places where somebody thought it was a good idea to implement a switch or if/else series on type rather than use polymorphism to distribute to the subclasses the type-specific behavior.

    Are we really gonna go back there? Or did the author completely miss the point? Is there some actual useful purpose here that doesn't violate polymorphism and OCP? Or did the JCP committee simply bowed to the pressure of the seeming overwhelming number of coders (I won't call them programmers or developers and definitely not engineers) who simply don't understand OO and want Java to become C with different reserved words like "record" instead of "struct".

  • Re: Aborted Polymorphism?

    by Javier Paniza,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    I agree. In the moment you use "instance of" something bad is happening. I would deprecated "instance of" instead of promoting the use of it.

  • Re: Aborted Polymorphism?

    by A N M Bazlur Rahman,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Thank you for your thoughtful comment. I understand where you're coming from, and it's indeed a valid point that relying too heavily on testing the instanceof a variable could potentially lead to scattered behavior throughout the codebase.

    However, the use of Java's record feature doesn't necessarily imply we're moving away from OO principles. Rather, it's another tool in our toolbox, aimed at providing a more concise way to create data carrier classes, where the primary purpose is to carry data.

    As for the comparison with C structs, while there are similarities, Java records come with additional capabilities such as automatic generation of standard methods (like equals, hashCode, toString), which can lead to more reliable and maintainable code.

    I do agree with you that these features need to be used judiciously, as they might tempt some to drift towards procedural style of coding. As with any feature, it is up to us as developers to use them wisely and in line with the overall design principles we're following.

    However, Java is going through an ongoing evolution, and one JEP is linked with another. I think If we really want to see the benefits holistically, I would recommend reading this article: www.infoq.com/articles/data-oriented-programmin...

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

BT