Running on Java 22-ea+27-2262 (Preview)
Home of The JavaSpecialists' Newsletter

300Break in Switch With Pattern Matching

Author: Dr Heinz M. KabutzDate: 2022-04-26Java Version: 18-previewCategory: Language
 

Abstract: The enhanced switch is cool. Pattern Matching for switch is super cool (they even timed the JEP number to be exactly 420 - or was that fate?). But what happens when we use "break" inside the case statement? Let's find out.

 

Welcome to the 300th edition of The Java(tm) Specialists' Newsletter, sent to you from a sunny Crete. And that is good, because for the last few years, every time it rained, our power would trip. Fortunately we live in one of the driest parts of Crete, which itself is rather dry. We "solved" the issue by turning off all the lights outside. After a day of rain, the sun came out again, and we could turn our lights back on. That's like rebooting to see if the race condition is gone. We are finally getting round to sorting that out. Seems that exposed wires from an ancient motion sensor could perhaps be the culprit!

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

Break in Switch With Pattern Matching

By now, most of us have seen pattern matching used with instanceof. For example, here is an old equals() method from Person:

public final boolean equals(Object o) {
  if (!(o instanceof Person)) return false;
  Person p = (Person) o;
  return firstName.equals(p.firstName)
      && lastName.equals(p.lastName)
      && dateOfBirth.equals(p.dateOfBirth);
}

And here is the new version with pattern matching on instanceof:

public boolean equals(Object o) {
  return o instanceof Person p
      && firstName.equals(p.firstName)
      && lastName.equals(p.lastName)
      && dateOfBirth.equals(p.dateOfBirth);
}

Much nicer, we no longer need the explicit cast.

In Java 18-Preview, we can do pattern matching on the new enhanced switch statement. Furthermore, if the classes are sealed, then the compiler will check whether we have covered all cases. Let's begin with a simple sealed interface that has four implementations.

package eu.javaspecialists.tjsn.issue300;

public sealed interface Colour {
  final class Red implements Colour {}
  final class Green implements Colour {}
  final class Blue implements Colour {}
  final class Orange implements Colour {}
}

I will be the first to admit that this example is a bit contrived. A better example is the Style interface that you will find in JDK-18. It has three inner records for three types of styles. Nevertheless, let's continue with our terrible example.

In the next class, we iterate over a list of Colour objects, and depending on the type, we print out the HTML equivalent of each colour. Note that the last else branch is dead code - since Colour is a sealed interface, only the four colours that are contained inside as nested classes will be allowed as implementations. Thus the AssertionError will never be thrown.

Another oddity is the break when we encounter the first Blue colour. The break will leave from the for loop.

import eu.javaspecialists.tjsn.issue300.Colour.*;

public class IfElseInstanceof {
  public static void main(String... args) {
    Colour[] colours = {new Red(), new Red(), new Green(),
        new Blue(), new Green(), new Orange()};
    for (Colour colour : colours) {
      if (colour instanceof Red) {
        System.out.println("#FF0000 // Red");
      } else if (colour instanceof Green) {
        System.out.println("#008000 // Green");
      } else if (colour instanceof Blue) {
        System.out.println("#0000FF // Blue");
        break; // stop when we encounter the first Blue
      } else if (colour instanceof Orange) {
        System.out.println("#FFA500 // Orange");
      } else {
        throw new AssertionError(
            "Unknown Colour: " + colour.getClass());
      }
    }
  }
}

The output when we run this code is:

#FF0000 // Red
#FF0000 // Red
#008000 // Green
#0000FF // Blue

Let's turn this into the enhanced switch, with pattern matching for instanceof. For more information of how this works, please refer to JEP 420 (and before the recent offer by Elon Musk for Twitter, I had no idea that 420 had another meaning ...)

One thing you will notice is that it is no longer necessary to check that we have covered all cases. If we forget one of the possible classes, then the compiler will complain.

import eu.javaspecialists.tjsn.issue300.Colour.*;

public class EnhancedSwitchWithPatternMatching {
  public static void main(String... args) {
    Colour[] colours = {new Red(), new Red(), new Green(),
        new Blue(), new Green(), new Orange()};
    for (Colour colour : colours) {
      switch (colour) {
        case Red r -> System.out.println("#FF0000 // Red");
        case Green g -> System.out.println("#008000 // Green");
        case Blue b -> {
          System.out.println("#0000FF // Blue");
          break; // stop when we encounter the first Blue
        }
        case Orange o -> System.out.println("#FFA500 // Orange");
      }
    }
  }
}

When we run it, we see this output:

#FF0000 // Red
#FF0000 // Red
#008000 // Green
#0000FF // Blue
#008000 // Green
#FFA500 // Orange

Huh? That's not correct. Why didn't it stop once it got to Blue?

The reason should be fairly obvious to those whose minds have been distorted by decades of programming in C or Java. In the old switch statement, if we did not add a break at the end of a case, then we would fall through to the next case. In the enhanced switch statement, the break is implied. Thus if we do add a break, should that not then apply to the context outside of the switch? It appears not. The reason is that we might want to have a fairly complex statement that breaks out of the switch at a different place besides the end.

An easy workaround is to add a label to the for-loop, and then we can do a labelled break to leave the loop altogether:

import eu.javaspecialists.tjsn.issue300.Colour.*;

public class EnhancedSwitchWithLabelledBreak {
  public static void main(String... args) {
    Colour[] colours = {new Red(), new Red(), new Green(),
        new Blue(), new Green(), new Orange()};
    out:
    for (Colour colour : colours) {
      switch (colour) {
        case Red r -> System.out.println("#FF0000 // Red");
        case Green g -> System.out.println("#008000 // Green");
        case Blue b -> {
          System.out.println("#0000FF // Blue");
          break out; // stop when we encounter the first Blue
        }
        case Orange o -> System.out.println("#FFA500 // Orange");
      }
    }
  }
}

And now the output is the same as the if-else statements:

#FF0000 // Red
#FF0000 // Red
#008000 // Green
#0000FF // Blue

Eureka!

Kind regards

Heinz

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...