Friday, December 10, 2021

Gavin Bierman explains pattern matching for switch, a Java 17 preview

Oracle Java Exam, Oracle Java Exam Prep, Oracle Core Java, Oracle Java Certification, Oracle Java Career

Pattern matching for switch follows logically from pattern matching for instanceof, which was delivered as part of JDK 16.

Download a PDF of this article

JEP 406 describes a Java feature previewed in JDK 17: pattern matching for switch. This feature follows logically from pattern matching for instanceof, which was delivered as part of JDK 16 by JEP 394.

Gavin Bierman, a consulting member of the Oracle technical staff based in the UK, explained the concepts behind pattern matching in general and, more specifically, pattern matching for switch. Bierman, who managed the JEP 406 process, works in the Java Platform Group helping design the next versions of Java. He is also a member of Oracle Labs’ Programming Language Research Group

Java Magazine: What’s the general concept behind pattern matching?

Bierman: Pattern matching is a big topic. We’re pushing pattern matching out into Java in small pieces so people can get used to it and see the beauty of it.

A pattern is something you can test a value against. A value will either match a pattern or not match a pattern. If a value matches the specified pattern, the pattern variable is initialized with the value it matched.

Java Magazine: In this context, what is a pattern variable?

Bierman: The pattern itself can contain holes because you don’t want to specify all the details of every part of the value. So, sometimes a developer puts placeholders in certain places within the pattern. We use variables to represent those holes. We call them pattern variables, but they’re really just local variables.

Java Magazine: That brings us to pattern matching for switch.

Bierman: Once we added pattern matching to instanceof, developers started using patterns in big if-then-else chains using instanceof. That works, but such if-then-else statements are messy, inelegant, and hard to maintain. For example

static String formatter(Object o) {

    String formatted = "unknown";

    if (o instanceof Integer i) {

        formatted = String.format("int %d", i);

    } else if (o instanceof Long l) {

        formatted = String.format("long %d", l);

    } else if (o instanceof Double d) {

        formatted = String.format("double %f", d);

    } else if (o instanceof String s) {

        formatted = String.format("String %s", s);

    }

    return formatted;

}

We’d like developers to create better code than that. We already have a feature in the Java language to allow for a multiplace conditional, that is, switch. The next natural location for us to enhance pattern matching is the switch statement.

The expression that we’re testing is called the selector expression. We have a block of code, and it has case labels. Prior to this preview feature for pattern matching for switch, those labels were constants. The idea is you evaluate the selector expression, then you find the label that is equal to the value of the selector expression, and then you execute the code associated with that case label.

Prior to the JEP 406 preview, switch restricted the types of selector expression. But that doesn’t make sense if we’re going to add pattern labels. Thus, we’re relaxing that restriction on switch. So now you can switch on anything. Here’s how much more concise and readable the previous code looks by using pattern matching.

static String formatterPatternSwitch(Object o) {

    return switch (o) {

        case Integer i -> String.format("int %d", i);

        case Long l    -> String.format("long %d", l);

        case Double d  -> String.format("double %f", d);

        case String s  -> String.format("String %s", s);

        default        -> o.toString();

    };

}

Java Magazine: What does this preview mean for the completeness of a switch statement?

Bierman: Completeness appeared with switch expressions that were finalized in Java 14. When you are evaluating any expression in Java, you expect it to result in either a value or an exception.

When we introduced switch expressions, we had a question in the design process: What if your cases don’t cover all the values that could arise from the thing you are switching over? That’s got to be an error.

But which error?

Rather than cause the application to always throw an exception in such a situation, we decided it would be useful for the compiler to flag a switch expression that was not complete.

Thus, we have a simple check for switch expressions that looks at all the case labels and asks, “Is there a case for every possible value in this expression?” Otherwise, it’s not going to be a valid expression. There might be a situation where we don’t know what the value is or what the exception will be.

As we extend switch with patterns, we get a similar question. Obviously, for switch expressions, we must enhance the notion of completeness to deal with the fact that the case labels are no longer just simple constants but can contain patterns. Therefore, we have lifted the notion of completeness from simple case constants to pattern labels.

One problem with switch statements, as they exist currently, is that if you’ve forgotten a case in a switch statement, the switch statement simply does nothing. It just carries on as before. This silent behavior leads to tough-to-debug situations where you realize some value hasn’t been set in the way you expected.

For switch expressions, that must not happen because switch requires the body to be complete. The feedback we’ve received from developers about switch expressions indicates they really like that the compiler does extra checks for them and spots mistakes. That’s why we decided that switch statements that use pattern labels must also be complete, just as for switch expressions.

Java Magazine: What can you tell us about null values here?

Bierman: There’s a long-standing issue in Java and in other languages like Java: the dreaded null value.

Prior to JEP 406, switch had a very important design feature: It threw a null pointer exception if the value of the selector expression was null—without looking at any of the body of the switch block. Null simply wasn’t permitted as an option.

Now that we’re enhancing switch to do pattern matching and more-complicated things coming in the future, forbidding null seems like an unsustainable design decision.

We’ve done two things. One is we don’t instantly throw that null pointer exception. Secondly, we’ve added a new case label so you can be even more explicit about null, if you want to be, which I think is going to be very useful. You can write case null in the switch body. If you use these new values, everything works smoothly.

This is a nonbreaking change: All nonpattern switch statements will continue to throw a null pointer exception when switching over null, and this will make switch a much more usable construct in the future.

Java Magazine: Speaking of things to come…

Bierman: This is the next step in pattern matching for Java, where patterns are not just solely asking about types but rather do more work by deconstructing the value for you. (This is a JEP that might target JDK 18.)

Records are a great candidate for this type of functionality and will be the first types that will support deconstruction patterns.

Records are special sorts of classes that have what we call components. All records have components that appear in the declaration. For example, if you say record Point, and then your components are X and Y, you know every point has a component X and a component Y.

In the future, we’re going to allow a pattern that not only asks whether a value is a Point but also specifies that if the value is indeed a Point, it will initialize pattern variables with the values of X and Y from that Point.

In other words, the pattern will break apart the value, deconstruct it into its component values, and assign the pattern variables you supplied with the values of the components.

It’s kind of like the opposite of a constructor. So then you can nest the patterns in the same way you can nest constructor cells. If you can nest constructors, you ought to be able to nest deconstructors. Developers can write beautiful-looking code, and they start to really see the power of pattern matching.

I encourage everybody to read the JEPs that are out there and to try out the preview of JEP 406. The JEP description page contains a lot more detail. Please give us feedback, because these are complicated features and significant changes to the language. We’d love to hear your experience and your thoughts on the design.

Source: oracle.com

Related Posts

0 comments:

Post a Comment