Pattern matching, switching null, guarded patterns, 26 hours of Java, and Accento

nipafx news #88 — 24th of May 2021

Nicolai Parlog
nipafx news
Published in
9 min readJun 11, 2021

--

Hi everyone,

this newsletter arrives at an unusual time. I’ve recently been gunning for each month’s last Sunday but realized yesterday that that’s not gonna work this time because I’ve got something quite time consuming planned for the coming weekend: A 26-hour live stream for Java’s 26th birthday! :) More on that and two other events after I tell the cool things JEP 406 will bring to Java 17.

JEP 406

JEP 406 is proposed to target Java 17, which means unless something unexpected happens and somebody raises objections until Wednesday, there’s three thrilling extensions to switch that we'll be able to explore from September on. Here they are.

(NB: The first is a slightly rewritten Twitter thread.)

Pattern matching in switch

Pattern matching is coming to switch, which means that this is gonna be possible:

// with `if` and type patterns - so Java 16
double area_if(Shape shape) {
if (shape instanceof Circle circle)
return circle.radius() * circle.radius() * Math.PI;
if (shape instanceof Rectangle rect)
return rect.width() * rect.height();
return Double.NaN;
}
// with `switch` and type patterns - preview in 17
double area(Shape shape) {
return switch (shape) {
// type pattern in switch tests against type and casts
case Circle circle ->
circle.radius() * circle.radius() * Math.PI;
case Rectangle rect ->
rect.width() * rect.height();
default -> Double.Nan;
};
}

(For this section to make sense, you need to know type patterns. If you don’t, check out this blog post about type patterns.)

Question: What can I do with this that I can’t do with if?
Answer: Nothing (as is generally the case for switch).

But you can express something that you can’t express with if, namely that you expect exactly one of the branches to be executed. Benefits: better readability, less room for errors, compiler support, better performance.

Question: So what happens if I use patterns with switch?
Answer: What you would expect: The runtime finds the first pattern that matches the variable and executes the subsequent code.

There are a few details to consider of course:

  • kinds of switches
  • variable scope
  • dominance
  • cooperation with sealed classes

Kinds of switches

A switch that uses only constants (like strings or enum values) as labels is called a normal switch. One that uses only pattern as labels is called a pattern switch.

// "normal switch"
String result = switch (ternaryBool) {
case TRUE, FALSE -> "makes sense";
default -> "makes no sense";
};
// "pattern switch"
String name = switch (shape) {
case Circle circle -> "circle";
case Rectangle rectangle -> "rectangle";
default -> "NOT A SHAPE";
}

You can’t mix constant and pattern labels in the same switch — it’s either or.

Variable scope

All variables that are declared in a switch label's pattern are in scope where the pattern applies (called flow scoping and works the same as in if).

That yields a surprising corner case and a natural limitation:

The corner case is fall-through, which you get if the switch uses colons (instead of arrows). If you fall through into a branch that has a label with a pattern, then that pattern's variables are in scope. But the pattern may not even apply! That's not good, so you get a compile error.

double area(Shape shape) {
String result = Double.NaN;
switch (shape) {
case Circle circle:
result = circle.radius() * circle.radius() * Math.PI;
// compile error because in the following
// branch, both `circle` and `rect` would
// be in scope even though `shape` may
// not be a `Rectangle`
// ~> use `break` to prevent fall-through
case Rectangle rect:
result = rect.width() * rect.height();
};
return result;
}

So in a pattern switch with colons, you must use break.

The limitation is that you can’t use multiple patterns in the same case (since Java 14 you can use multiple constants in a case). If you could, multiple variables needed to be in scope in the subsequent code even though only one pattern may have matched.

String isShape = switch (shape) {
// compile error because the neither `circle`
// nor `rectangle` can be in scope on the
// right side because neither pattern is
// guaranteed to have matched
case Circle circle, Rectangle rectangle -> "Yes";
default -> "No";
}

Dominance

A new concept is that of dominance. A pattern P dominates another pattern Q if “Q matches” implies “P matches”. In other words, if P doesn’t match neither does Q.

You can have both in the same switch if P comes after Q to catch remaining cases. The other way around would lead to dead code, though, because if P doesn’t match, neither does Q. That’s pointless and likely a mistake, so the compiler helps you prevent it by throwing an error.

double area(Shape shape) {
return switch (shape) {
case Circle circle ->
circle.radius() * circle.radius() * Math.PI;
case Rectangle rect ->
rect.width() * rect.height();
// if `Square extends Rectangle`, this
// branch would be unreachable; the
// compiler knows that, so it's an error
case Square square ->
square.length() * square.length();
default -> Double.Nan;
};
}

Cooperation with sealed types

As you can see, the compiler understands type relationships. That comes in real handy if you throw in sealed types (which are standardized in Java 17, btw): If a switch expressions' patterns cover all permitted subtypes of a sealed type, you don't need to include a default branch!

I’m really looking forward to using pattern matching in switch expressions in practice. With sealed types, type patterns, and switch expressions, Project Amber’s long game is slowly coming together.

But that’s not even all JEP 406 has to offer.

Handling null

Up to now, switch would throw a NullPointerException when the switched-over variable is null and because of backwards compatibility, that's of course not going to change. Still, JEP 406 makes most of that problem go away by allowing null as a label:

double area(Shape shape) {
return switch (shape) {
case Circle circle -> // [...]
case Rectangle rect -> // [...]
case null -> Double.Nan;
default -> Double.Nan;
};
}

If null is a common occurrence in your code base, you'd have to prepend the switch with a null-check for shape, but thanks to JEP 406 you don't need to! Just add case null and the runtime will execute that branch in case shape is null.

While it seems to be so very elegant, the default branch does not include the null case (otherwise all existing switches would radically change their behavior, which... waves at backwards compatibility). But you can combine the two branches!

double area(Shape shape) {
return switch (shape) {
case Circle circle -> // [...]
case Rectangle rect -> // [...]
case null, default -> Double.Nan;
};
}

I’m pretty sure that’s gonna become the new norm in many code bases. Not mine, I want NPEs for my nulls.

Guarded patterns

The examples so far all just needed to discern by type, but in real-life code, you will often want to further differentiate instances based on some other properties:

String type(Shape shape) {
return switch (shape) {
case Circle circle ->
// need to check something else
circle.radius() > 5
? "large circles"
: "circle";
case Rectangle rect -> "rectangle";
case null, default -> "unknown";
};
}

It’s unfortunate to split the tests for circle-ness and largeness into two different parts with one before and one after the arrow. On the other hand, it’s fortunate that we don’t have to do that ;) :

String type(Shape shape) {
return switch (shape) {
// this will work as well!
case Circle circle
&& circle.radius() > 5 -> "large circles"
case Circle circle -> "circle circles"
case Rectangle rect -> "rectangle";
case null, default -> "unknown";
};
}

Java could implement this by enhancing the case label, so it can check additional boolean conditions. That grammar would result in the following interpretation of the label above:

//  |---------------- case -----------------|
// |------------- label --------------|
// |- pattern -| |- boolean expr. -|
case Circle circle && circle.radius() > 5 ->

As you can see, in that hypothetical grammar the && and boolean expression would belong to the label. Java takes a different approach and instead extends the pattern syntax:

//  |---------------- case -----------------|
// |------------- label --------------|
// |------------ pattern -------------|
// |- type p. -| |- boolean expr. -|
case Circle circle && circle.radius() > 5 ->

The cool thing about that is that (a) it keeps the switch simpler and (b) makes patterns more powerful in general. Wherever you can use them, you will be able to append additional checks in the form of boolean expressions. For example:

//        |------...
if (shape instanceof
// ...----------- pattern --------------|
(Circle circle && circle.radius() > 5)) {
// [...]
}

Ok, that’s not very impressive because you can just leave out the parenthesis and use regular boolean operations:

//        |------ pattern -------|
if (shape instanceof Circle circle
// |- boolean expr. -|
&& circle.radius() > 5) {
// [...]
}

But it will be more impressive when you can start to nest pattern for destructuring in some future Java version, for example:

// strawman syntax ahead!
if (shape instanceof ColoredShape(
Circle circle && circle.radius() > 5,
Color color)) {
// handle large, colored circle
}

Let’s see how that turns out.

26 hours of Java

Last year’s 25-hour live stream for Java’s 25th birthday was a lot of fun! But it was also quite time-consuming to prepare and I basically used all the talks I ever created to fill the time. :D So this year, it’s not a marathon, it’s a relay race.

I’ll start on May 28th at 0600 UTC and throughout the next 26 hours hand over the baton to Sebastien Blanc, Billy Korando, and Ted M. Young, who’ll each stream on their channels. For times, links, and the schedule check this announcement bit needless to say, there’s gonna be a lot of Java.

hack.commit.push

Part of the 26 hours will be my end of hack.commit.push, where I’ll participate as a maintainer of JUnit Pioneer (Saturday, 0700–1300 UTC). So if you’re interested in creating JUnit 5/Jupiter extensions, why not try it out?

Accento

Then there’s Accento. After we had to move online last year because of the pandemic, we’re going back to the roots and plan an in-person conference in Karlsruhe’s Südwerk on September 28th/29th. It’s gonna be very cool! Venkat Subramaniam will give the keynote (not yet on the website), Holly Cummins will talk about performance, we have Nicolas Frankel on Change-Data-Capture, Emily Jiang on MicroProfile, Peter Kröner on JS generator functions, and a few more amazing speakers. You should come!

Now, if I’d be a good marketer and all-out well-prepared person, I’d have your discount code ready, so you could get your ticket right away. But if you’ve been reading this newsletter for a while, you know that’s not me. I didn’t realize that I’d want to tell you about Accento (but I want to because it’s so very cool! (I already mentioned that didn’t I? (How many parenthesis can I nest?))) and so I didn’t prepare the code and I can’t fix that on my own. Damn.

So, if you’re interested, please don’t get your ticket yet. Check out the conference, get ready to pounce, and I’ll have your code for you in the next newsletter. Until then, you can follow AccentoDev on Twitter.

State of the theme

I’m writing this on Pentecost Monday (supposed to be a day off in Germany) at ten in the night… let’s just say the adhering-to-the-plan part didn’t go that well. Shocker, I know.

Shots

No new Instruments episode (btw, do you like those?), no Project Showcase, no list of my accomplishments, very few Shorts — ain’t nobody got time for that.

But I wish you a great few coming weeks, particularly if you have some time off. Maybe you’re one of the lucky ones and are already traveling. I’m imagining the blue ocean, white sands, tropical beaches… have a good time! :)

so long … Nicolai

PS: Don’t forget to subscribe or recommend! :)

--

--

Nicolai Parlog
nipafx news

Nicolai is a #Java enthusiast with a passion for learning and sharing — in posts & books; in videos & streams; at conferences & in courses. https://nipafx.dev