Java switch case with null

Java 21 – Pattern Matching for Switch

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.

  • 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:

Java 17 , then 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.

This is now finalized in Java 21. It is no longer a preview feature but a part of Java language.

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.

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)); >public static 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"; >; >

Here is the output:

Gaurded Patterns

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.

case House house: if(house.getNoOfWindows() > 4)

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

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

In the above case statement ,

House house is called a Pattern and

Читайте также:  Entity to dto mapper java

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

A pattern and a boolean expression together joined by “when” keyword is called a “Gaurded Pattern”

Null Cases

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

Now Java 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.

You can also include both null and default cases together:

case null,default -> “Nothing matches”;

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!

The below switch block throws error:

static int coverage(Object obj) < return switch (obj) < case String s ->s.length(); >; >

This is because the above code includes only String object , there are other types of objects like Integer etc.

So how can you handle all possible options.

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

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

But Java recommends to include all possible case types.

In real world use cases you probably won’t include “Object” type in your switch expression.

You might use a custom class type.

In that case you just need to make sure that all implementations of the class type is included in the case labels.

sealed interface S permits A, B, C <> final class A implements S <> final class B implements S <> final class C implements S <> static int testSealedExhaustive(S s) < return switch (s) < case A a ->1; case B b -> 2; case C c -> 3; >; >

In the above code we are creating a sealed interface which permits only three classes A, B and C to inherit from it.

So it is a good practice to include all the three classes in the switch block.

Notice that there is no default keyword in the above switch block.

That is because we have included all the possible types.

If we had missed any one of the classes , then the above code won’t compile.

Dominance of case labels:

Let’s consider the following switch expressions:

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

Do you think the above will work?

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 starting java 17, you get stopped at the compiler level and throws the below error :

The label “House s” dominates the label “House s && s.getNoOfWindows() > 4” and Java 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.

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:

Читайте также:  Java parse xml path

Источник

Branching with Switch Expressions

In Java SE 14 you can use another, more convenient syntax for the switch keyword: the switch expression.

Several things have motivated this new syntax.

  1. The default control flow behavior between switch labels is to fall through. This syntax is error-prone and leads to bugs in applications.
  2. The switch block is treated as one block. This may be an impediment in the case where you need to define a variable only in one particular case .
  3. The switch statement is a statement. In the examples of the previous sections, a variable is given a value in each case . Making it an expression could lead to better and more readable code.

The syntax covered in the previous section, known as switch statement is still available in Java SE 14 and its semantics did not change. Starting with Java SE 14 a new syntax for the switch is available: the switch expression.

This syntax modifies the syntax of the switch label. Suppose you have the following switch statement in your application.

int day = . ; // any day int len = 0; switch (day) < case MONDAY: case FRIDAY: case SUNDAY: len = 6; break; case TUESDAY: len = 7; break; case THURSDAY: case SATURDAY: len = 8; break; case WEDNESDAY: len = 9; break; >System.out.println("len = " + len); 

With the switch expression syntax, you can now write it in the following way.

int day = . ; // any day int len = switch (day) < case MONDAY, FRIDAY, SUNDAY ->6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; > System.out.println("len = " + len); 

The syntax of switch label is now case L -> . Only the code to the right of the label is executed if the label is matched. This code may be a single expression, a block, or a throw statement. Because this code is one block, you can define variables in it that are local to this particular block.

This syntax also supports multiple constants per case, separated by commas, as shown on the previous example.

Producing a Value

This switch statement can be used as an expression. For instance, the example of the previous section can be rewritten with a switch statement in the following way.

int quarter = . ; // any value String quarterLabel = switch (quarter) < case 0 ->"Q1 - Winter"; case 1 -> "Q2 - Spring"; case 2 -> "Q3 - Summer"; case 3 -> "Q3 - Summer"; default -> "Unknown quarter"; >; 

If there is only one statement in the case block, the value produced by this statement is returned by the switch expression.

The syntax in the case of a block of code is a little different. Traditionally, the return keyword is used to denote the value produced by a block of code. Unfortunately this syntax leads to ambiguity in the case of the switch statement. Let us consider the following example. This code does not compile, it is just there as an example.

// Be careful, this code does NOT compile! public String convertToLabel(int quarter) < String quarterLabel = switch (quarter) < case 0 ->< System.out.println("Q1 - Winter"); return "Q1 - Winter"; >; default -> "Unknown quarter"; >; > return quarterLabel; > 

The block of code executed in the case where quarter is equal to 0 needs to return a value. It uses the return keyword to denote this value. If you take a close look at this code, you see that there are two return statements: one in the case block, and another one in the method block. This is where the ambiguity lies: one may be wondering what is the semantics of the first return . Does it mean that the program exits the method with this value? Or does it leave the switch statement? Such ambiguities lead to poor readability and error-prone code.

Читайте также:  WhatsApp Icon

A new syntax has been created to solve this ambiguity: the yield statement. The code of the previous example should be written in the following way.

public String convertToLabel(int quarter) < String quarterLabel = switch (quarter) < case 0 ->< System.out.println("Q1 - Winter"); yield "Q1 - Winter"; >; default -> "Unknown quarter"; >; > return quarterLabel; > 

The yield statement is a statement that can be used in any case block of a switch statement. It comes with a value, that becomes the value of the enclosing switch statement.

Adding a Default Clause

Default clauses allow your code to handle cases where the selector value does not match any case constant.

The cases of a switch expression must be exhaustive. For all possible values, there must be a matching switch label. Switch statements are not required to be exhaustive. If the selector target does not match any switch label, this switch statement will not do anything, silently. This may be a place for bugs to hide in your application, something you want to avoid.

In most of the cases, exhaustiveness can be achieved using a default clause; however, in the case of an enum switch expression that covers all known constants, you do not need to add this default clause.

There is still a case that needs to be dealt with. What would happen if someone adds an enumerated value in an enumeration, but forget to update the switch statements on this enumeration? To handle this case, the compiler adds a default clause for you in exhaustive switch statements. This default clause will never be executed in normal cases. It will be only if an enumerated value has been added, and will throw an IncompatibleClassChangeError .

Handling exhaustiveness is a feature of switch expressions that is not provided by traditional switch statements and that is used in other cases than switch on enumerated values.

Writing Colon Case in Switch Expressions

A switch expression can also use a traditional case block with case L: . In this case the fall through semantics does apply. Values are yielded using the yield statement.

int quarter = . ; // any value String quarterLabel = switch (quarter) < case 0 : yield "Q1 - Winter"; case 1 : yield "Q2 - Spring"; case 2 : yield "Q3 - Summer"; case 3 : yield "Q3 - Summer"; default: System.out.println("Unknown quarter"); yield "Unknown quarter"; >; 

Dealing with Null Values

So far, switch statements do not accept null selector values. If you try to switch on a null value you will get a NullPointerException .

Java SE 17 has a preview feature that enhances switch expressions to allow for null values, so you can expect this situation to change.

Источник

Оцените статью