Switch Pattern Matching in Java
The release of Java 17 has brought us new features expanding switch
functionality. Since this version, we have pattern matching as a preview feature. This update will expand our capabilities when working with switch
. In this topic, we will explore these enhancements to help you master them.
Pattern matching semantics
The pattern matching implementation for switch
is based on the instanceof
operator enhancements. It allows us to perform runtime type checking and other required operations, for example, printing a message. This feature can improve code readability and achieve the same functionality with fewer lines of code.
Let's look at the code snippet below with if
blocks. Later we will write the same code using the new switch
feature.
public class SwitchPatternMatchingDemo {
public static void main(String[] args) {
Object obj = "Java";
ifTypeCheckingDemo(obj); // String: Java
}
static void ifTypeCheckingDemo(Object o) {
if (o instanceof Integer i) {
System.out.printf("int: %d", i);
} else if (o instanceof Double d) {
System.out.printf("double: %f", d);
} else if (o instanceof String s) {
System.out.printf("String: %s", s);
} else {
System.out.printf("No Match!");
}
}
}
In this example, we pass a variable to the ifTypeCheckingDemo(Object o)
method, where it is checked against three specific data types and the method prints a corresponding message. In our case, that message is String: Java
. Now, take a look at the same piece of code written using switch
pattern matching:
public class SwitchPatternMatchingDemo {
public static void main(String[] args) {
Object obj = "Java";
switchTypeCheckingDemo(obj); // String: Java
}
static void switchTypeCheckingDemo(Object o) {
switch (o) {
case Integer i -> System.out.printf("int: %d", i);
case Double d -> System.out.printf("double: %f", d);
case String s -> System.out.printf("String: %s", s);
default -> System.out.println("No Match!");
}
}
}
This code is much more concise and readable, which is a good reason to pay attention to this feature.
Selector expression type checking enhancement
The example in the previous section is written using basic Java types. But what if we need to perform type checking with another non-basic type? Before this update, switch
supported only primitive types and their wrapper classes, as well as String and Enum type. This enhancement allows us to use any type in the scope of pattern matching.
public class SwitchPatternMatchingDemo {
public static void main(String[] args) {
Object obj = new Person("James Gosling");
switchTypeCheckingDemo(obj); // Person: Person{name=James Gosling}
}
static void switchTypeCheckingDemo(Object o) {
switch (o) {
case Integer i -> System.out.printf("int: %d", i);
case Person p -> System.out.printf("Person: %s", p.toString());
case String s -> System.out.printf("String: %s", s);
default -> System.out.println("No Match!");
}
}
}
class Person {
private String name;
// getter and setter
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name=" + name +
'}';
}
}
This application will compile and print Person: Person{name=James Gosling}
, which was impossible before the update.
Pattern matching via logical operators
Pattern matching for switch
, like in the case of the instanceof
operator, isn't limited to single type checking. We can also use the &&
logical operator and add an extra statement level:
public class SwitchPatternMatchingDemo {
public static void main(String[] args) {
Object obj = 100.0;
switchTypeCheckingDemo(obj); // double: 100,000000, the number is positive
}
static void switchTypeCheckingDemo(Object o) {
switch (o) {
case Integer i -> {
if (i > 0) {
System.out.printf("int: %d, the number is positive", i);
}
}
case Double d && d > 0 -> System.out.printf("double: %f, the number is positive", d);
default -> System.out.println("No Match!");
}
}
}
Inside the switchTypeCheckingDemo(Object o)
method there are two case labels. The first one uses a pattern to check the type, but the print statement is executed in the if
block. The second case does the same thing but uses a pattern for the second statement to check whether the variable value is a positive number.
By the way, switch
pattern matching, just like the instanceof
operator, doesn't support the ||
operator due to its semantics.
Null operations
In older Java versions, switch
statements couldn't handle null
values. The following code block will run but throw a NullPointerException
as soon as the application executes the first line of the nullDemo(String s)
method.
public class SwitchPatternMatchingDemo {
public static void main(String[] args) {
String str = null;
nullDemo(str);
}
static void nullDemo(String s) {
switch (s) {
case "Hello" -> System.out.println("null");
case "Hi" -> System.out.println("String");
default -> System.out.println("No Match!");
}
}
}
On the other hand, if str
is not null
but we add a null
case, the code will not even run. We will get a compilation error:
public class SwitchPatternMatchingDemo {
public static void main(String[] args) {
String str = "Hello";
nullDemo(str);
}
static void nullDemo(String s) {
switch (s) {
case null -> System.out.println("null"); // Compilation error
case "Hello" -> System.out.println("Hello");
case "Hi" -> System.out.println("Hi");
default -> System.out.println("No Match!");
}
}
}
The new features enable us to avoid these restrictions. We can have a null
case label: the example below will compile successfully and we will face a NullPointerException
only in one condition - if the selector expression is null
and we don't have a null
case.
publiec class SwitchPatternMathchingDemo {
public static void main(String[] args) {
String str = null;
nullkDemo(str); // null case
}
static void nullDemo(String s) {
switch (s) {
case null -> System.out.println("null case");
case "Hello" -> System.out.println("Hello");
case "Hi" -> System.out.println("Hi");
default -> System.out.println("No Match!");
}
}
}
What do you think will be the result of running this application? Right: the code above will compile and print the null case
message!
Conclusion
The enhancements introduced in Java 17 pattern matching for switch
offer developers significant improvements in code clarity, flexibility, and maintainability. By allowing more concise syntax, support for complex data types, and even the ability to handle null
cases safely, these updates enable more readable code, that improves overall code quality
Pattern matching also extends the power of switch
beyond simple type checking, allowing logical operators like &&
to be incorporated and offering deeper flexibility in conditionals. Whether you're working with basic types, custom objects, or even null values, these new capabilities simplify many common programming tasks and eliminate potential errors like NullPointerExceptions
.
As you continue to explore and utilize these features, you'll find that they not only reduce boilerplate code but also help improve the efficiency of your Java programs. Pattern matching for switch
is a powerful tool that encourages more expressive and robust solutions for real-world software development challenges. Make sure to integrate these features into your workflow, and enjoy the enhanced programming experience they provide!