Pattern Matching for Switch – Java 17

Switch statement in java has gone through a rapid evolution since Java 7.

You could compare only integers until Java 7.

And then Java 8 allowed you to compare strings and enums as well.

And then Java 12 introduced a flurry of new features:

  • You can return values from a switch block and hence switch statements became switch expressions
  • You can have multiple values in a case label
  • You can return value from a switch expression through the arrow operator or through the “break” keyword

Java 13 later introduced yield keyword to be used instead of break keyword for returning values.

All these are explained here with examples:

The evolution of switch statement

Java 17 , the latest Java version, has introduced a new feature for switch .

It is called pattern matching.

You can match patterns in a case label.

In other words you can pass objects in switch condition and this object can be checked for different types in switch case labels.

Here is an example:

	public String test(Object obj) {
		
		
		return switch(obj) {

			
		
		case Integer i -> "It is an integer";

		case String s -> "It is a string";
		

		case House s -> "It is a house";

		default -> "It is none of the known data types";
		
		};
		
	}

In the above example I am passing an object to the switch condition. This was not possible until Java 17. And then this object can be checked for a particular data type and assigned to a variable as well.

For example consider the case :

   case Integer i- > "It is an integer";

The passed object is checked for the type “Integer” and then assigned to the variable “i” if it is an integer. And through the arrow operator the string “It is an integer ” is returned .

The above case can also be written as :

	
		case Integer i :
		   yield  "It is an integer";

yield statement was introduced in Java 13 to return values from switch expressions.

You can also deal with your own custom objects inside switch statements/expressions.

I created a class named “House” like below:

public class House{


	private int noOfWindows;

	private int noOfDoors;


	public House(int noOfWindows,int noOfDoors){

		this.noOfWindows = noOfWindows;
		this.noOfDoors = noOfDoors;
	}

	public int getNoOfWindows(){

		return noOfWindows;
	}

	public int getNoOfDoors(){

		return noOfDoors;
	}
}

I added a case label for “House” object type as well if you had noticed in the switch block.

Now let’s test this:

	public static void main(String a[]) {

		System.out.println(test(65));

		System.out.println(test("Hello Java 17"));

		System.out.println(test(true));

		House house = new House(4,2);

		System.out.println(test(house));

	}

Here is the output:

It works!

Gaurded Patterns

Let’s take this use case.

Inside the case label where I have checked for a “House” instance , I want to do an additional check.

Thinking traditionally , you could be doing this after the case statement.

Something like this:

case House house:

              if(house.getNoOfWindows() > 4) {
                   yield "It is a large house";
            }


But Java 17 has introduced “Guarded Patterns” . You can do this check in the case label itself:

case House house && house.getNoOfWindows() > 4 -> "It is a large house";

In the above case statement ,

House house is called a Pattern and

house.getNoOfWindows() > 4 is a boolean expression.

A pattern and a boolean expression together is called a “Gaurded Pattern”

Paranthesized pattern

If you want to have multiple checks in a case label you can do them using Paranthesized pattern . Here you just put all your boolean expressions inside paranthesis.

For example :

case House house && (house.getNoOfWindows() > 4 && house.getNoOfDoors() > 2) -> "It is a big house";

Null Cases

You could never pass a null value to switch statements prior to Java 17 without a Null pointer exception being thrown.

Java 17 allows you to handle it this way:

case null -> "It is a null object";

If you have the above switch expression you will never get Null Pointer exception if the object you pass is null.

Completeness of Patterns:

A switch expression requires that you handle all possible values of the object passed.

So if you are passing an “Object” instance you need to check for all types of objects!

How is this possible?

You can handle it by using the “default” case.

In case if you don’t include the default case , the compiler will complain:

Dominance of Pattern Labels:

Let’s consider the following switch expressions:

case House s - " It is a house";

case House s && s.getNoOfWindows() > 4  -> "It is a big house";

Do you think the above will work?

No, it won’t .

This is because if you pass a house instance both the cases will be executed if the house has more than 4 windows. A house with more than four windows is still a house!

To avoid this java 17 stops you at the compiler level and throws the below error :

The label “House s” dominates the label “House s && s.getNoOfWindows() > 4” and Java 17 doesn’t allow that.

A dominating label should always be placed last.

Hence the above case statements should be written in the below order:

case House s && s.getNoOfWindows() > 4 -> "It is a big house";
case House s -> "It is a house";

In this case when the first condition matches the switch expression returns and skips the rest labels.

That’s it!

Here is the entire code :

package java17;

public class SwitchPatternMatching {

	static public String test(Object obj) {
		
		
		return switch(obj) {

		
			case null -> "The object is null";
		
		case Integer i -> "It is an integer";
			
		case String s ->"It is a string";
		
		
		
		case House s && s.getNoOfWindows() > 4 -> "It is a big house";

		case House s -> "It is a house";
		

		default -> "It is none of the given data type";
		
		};
		
	}

	public static void main(String a[]) {

		System.out.println(test(65));

		System.out.println(test("Hello Java 17"));

		System.out.println(test(true));


		System.out.println(test(new House(4,2)));

		System.out.println(test(new House(5,5)));

		System.out.println(test(null));

	}
}


public class House{


	private int noOfWindows;

	private int noOfDoors;


	public House(int noOfWindows,int noOfDoors){

		this.noOfWindows = noOfWindows;
		this.noOfDoors = noOfDoors;
	}

	public int getNoOfWindows(){

		return noOfWindows;
	}

	public int getNoOfDoors(){

		return noOfDoors;
	}
}

and the output:

Pattern Matching is a preview feature , so you need to set preview feature flag on, while executing the above code.


Posted

in

, ,

by

Tags:

Comments

Leave a Reply

%d bloggers like this: