This is the part 2 of the blog covering switch expressions in Java. If you haven't read part 1 yet, please give it a read here as it would cover some of the basic understanding of the switch expression and pattern matching in switch.
Switch Exhaustiveness
Java compiler doesn't force exhaustiveness in switch statements, whereas switch expression are expected to be exhaustive. What really is exhaustiveness? Exhaustiveness ensures that all possible cases are handled in a switch expression, either explicitly or with a default case. For example, When you use an enum type, switch must cover different values of the enum. This is not limited to enums, its applicable to all the types switch supports. Look at the below versions of code, former one with switch statement and the later with switch expressions.
This snippet of code compiles and runs fine, without any complaints for not having a case for GREEN.
enum Color {
RED, GREEN, BLUE
}
private static String switchForEnum(Color color) {
String hexCode = null;
switch (color) {
case RED:
System.out.println("Color is red..");
hexCode = "#FF0000";
break;
case BLUE:
System.out.println("Color is blue");
hexCode = "#0000FF";
break;
}
return hexCode;
}
Whereas below code will fail to compile with an error: the switch expression does not cover all possible input values. Switch expressions are expected to be exhaustive and the code that does not cover all possible cases, or lacks a Match-All case will result in compiler error. Exhaustiveness enforced in switch expressions because, unlike switch statements, switch expressions are expected to return a value or throw an exception. In the cases, where a case covering a particular value, or a match-all case is missing, switch wont be returning anything and it will cause runtime errors.
private static String switchExpForEnum(Color color) {
return switch (color) {
case RED -> {
System.out.println("Color is red..");
yield "#FF0000";
}
case BLUE -> {
System.out.println("Color is blue");
yield "#0000FF";
}
// Error can be fixed by uncommenting any of the below code blocks
/*
case GREEN -> {
System.out.println("Color is green");
yield "#00FF00";
}
*/
/*
default -> {
System.out.println("Color is unknown");
yield "#FFFFFF";
}*/
};
}
The above code can be corrected by either adding a case for handling color GREEN or by adding a match-all case. Its better to add explicit case for color GREEN than a default, as default case would sweep issues under the rug if you may have to introduce more values to the enum in the future.
Have you noticed that there are no break statements, and there is new term yield?
switch statements are naturally fall-through, ie. without break statements between cases, execution would naturally continue to all the cases below the one that's matched until it encounters break or end of switch. This can cause for lot of troubles and switch expressions are designed to eliminate the fall-through behavior, so there is no need for break making the code concise and safe.
yield is a restricted identifier (not a keyword) introduced to return a value from the statement group matching a case in switch expression, similar to the keyword return used in switch statements.
Records patterns in switch
We have seen Typed Pattern in switch expression in the part 1 of the blog.
Records patterns allows pattern matching for record types and deconstruction of the record components directly in a switch expression. This feature is finalized in Java 21. Lets look at an example for the same.
sealed interface Quad permits Rectangle, Square, Kite {
static double calculatePerimeter(Quad quad) {
return switch(quad) {
case Rectangle(double len, double bre) -> 2 * (len + bre);
case Square(double s) -> 4 * s;
case Kite(double shortSide, double longSide, _, _) -> 2 * (shortSide + longSide);
};
}
}
record Rectangle(double length, double breadth) implements Quad {}
record Square(double side) implements Quad {}
record Kite(double short_side, double long_side, double short_diag, double long_diag) implements Quad {}
Quad is a sealed interface which allows 3 records Rectangle, Square and Kite (classes with immutable data components) to implement its functionalities. Check the usage of the Record pattern in switch expression inside calculatePerimeter method.
- Given switch expression takes a Quad type and its exhaustive, covering all the allowed record types. If you were to allow another record type for Quad, matching case arm should be added to the switch.
- The labels used in the above cases are called Record Patterns. Observe that the components of the records are deconstructed into 1 or more pattern variables and are available directly to use.
- Notice the usage of "_" in the last case. If there is any data component that you don't use in your logic, it need not be assigned to any variable. So, if you add an underscore, they are just ignored.
Nested record patterns
Above examples shows top level pattern matching. Records can be nested too ie.. one record can be a component of another record. Switch expressions allows nested pattern matching and nested deconstruction as well. Lets see it as an extension of the above code snippet.
sealed interface Quad permits Rectangle, Square, Kite, Parallelogram {
static double calculatePerimeter(Quad quad) {
return switch(quad) {
// assuming all cases exists for Rectangle, Square and Kite from previous code
case Parallelogram(Coordinate(double x1, double y1), Coordinate(double x2, double y2)) ->
2 * (Math.hypot(x1, y1) + Math.hypot(x2, y2));
};
}
}
record Coordinate(double x, double y) {}
record Parallelogram(Coordinate p1, Coordinate p2) implements Quad {}
Now, we have one more Quad variant - Parallelogram which has 2 nested components p1 and p2, instances of Coordinate record. In the switch expression, we added a case to perform top level matching of the Parallelogram type, and also deconstructs its nested Coordinate record components into their double types. These are nested records patterns.
We have seen how switch expressions makes the code more concise and functional, enhancing the safety and readability. Thank you reading up until this point. If you would like to read more such blogs or share your thoughts, write me at LinkedIn or Email.
Top comments (0)