The selection statements described in the previous article allow choosing and executing one among different (groups of) statements based on the evaluation of a condition, such as a boolean expression. Likewise, the switch statement allows the selection and execution of a group of statements based on the matching between an expression and different patterns.
switch (EXPRESSION)
{
case PATTERN1:
// statements associated with PATTERN1
case PATTERN2:
// statements associated with PATTERN2
case PATTERN3:
// statements associated with PATTERN3
// more case sections [...]
default:
// statements executed when there is no matching
}
According to the above syntax, after the switch
keyword, the EXPRESSION to be matched is specified in parenthesis. Curly braces indicate the beginning {
and the end }
of the construct. Multiple sections are defined as part of the switch
using the case
keyword, followed by the associated pattern. The statements belonging to these sections are not enclosed in curly braces but are just enlisted after the :
symbol. A switch
statement executes the statements in the first case
section whose pattern matches EXPRESSION.
Constant Pattern Matching
The only pattern matching supported before Java version 21 and C# version 9.0 was based on constant labels, as shown below.
public static void main(String[] args)
{
int day = // a value is assigned
switch (day)
{
case 1:
System.out.println("Monday");
break;
case 2:
System.out.println("Tuesday");
break;
case 3:
System.out.println("Wednesday");
break;
case 4:
System.out.println("Thursday");
break;
case 5:
System.out.println("Friday");
break;
case 6:
System.out.println("Saturday");
break;
case 7:
System.out.println("Sunday");
break;
default:
System.out.println("Invalid day number!");
}
}
In this example, the expression—the content of the variable day
—is evaluated and matched against all the labels defined in the case
sections. Then, the case
section with the matching label is executed.
It should be noted that the evaluation of the expression against the case
labels can be optimised to avoid going through all the defined cases. This can be done using a jump table, which is faster than the sequential evaluation in an if-else-if statement described in a previous article.
After a matching case
is executed, the program will continue to execute the following case
sections, even if their labels do not match the expression. This behaviour is known as fall through and, depending on the program logic, may not always be desired. Therefore, as shown in the example above, a break
statement is typically used at the end of each case
section to exit the switch
after a case
is executed.
As a result, in the above program, only one case
section will be executed when the program runs. Specifically, if day
is 1, the program prints “Monday” and then terminates; if day
is 2, the program prints “Tuesday” and then terminates; if day
is 3, the program prints “Wednesday” and then terminates (and so on for the other cases).
The above listing also defines a default section. This is an optional section whose associated statements are executed when an expression does not match any case
pattern. If an expression does not match any case
pattern and there is no default
section, the control falls out of the switch
statement. In the above example, if day
does not match any of the specified cases (i.e., it is not between 1 and 7), the default
case is executed, printing “Invalid day number!”.
Multiple case labels
Multiple labels can be specified at the beginning of a case
section; the statements in that section will be executed if the result of the expression matches any of those labels. This is useful when you want to group several cases that should result in the same action.
public static void main(String[] args)
{
int day = // a value is assigned
switch (day)
{
case 1:
case 2:
case 3:
case 4:
case 5:
System.out.println("Weekday");
break;
case 6:
case 7:
System.out.println("Weekend");
break;
default:
System.out.println("Invalid day number!");
}
}
}
The above code is designed to categorise the days of the week into weekdays and weekends based on the value of day
. If the value does not correspond to a valid day (1-7), it indicates an invalid input. If day
is 1, 2, 3, 4, or 5, the program will print “Weekday”. The break
statement is used to exit the switch
after executing the code for these cases. If day
is 6 or 7, the program will print “Weekend”. Again, the break
statement allows the flow to fall out of the switch
. If day
does not match any of the specified cases (1-7), the program will print “Invalid day number!”.
Using multiple labels per case
is efficient and makes the code cleaner and easier to read. Instead of writing separate blocks for each day, you group them when they share the same outcome. This reduces redundancy and improves maintainability. The output statements in the above section will be executed if the content of day
matches any of the defined labels for that section, which is similar to the logical OR introduced with the boolean expressions earlier.
Relational Pattern Matching
In our previous article, we used the if-else-if statement to write a program that prints a message on the screen according to the content of an int
variable temperature
.
public static void main(String[] args)
{
int temperature = 15 // a value is assigned
if (temperature > 40)
{
System.out.println("Weather is hot");
}
else if (temperature > 20)
{
System.out.println("Weather is warm");
}
else
{
System.out.println("Weather is cold");
}
}
Newer versions of Java (21) and C# (9.0) have introduced a new way to use the switch
statement to evaluate an expression against a relational pattern. This feature allows you to implement the same logic as the above if-else-if statement with a switch
. However, there are some differences in the syntax of this feature in these two languages.
Switch with relational pattern matching in Java
The following code provides an example of relational pattern matching in Java.
public static void main(String[] args)
{
Integer temperature = // a value is assigned
switch (temperature)
{
case Integer t when t > 40:
System.out.println("Weather is hot");
break;
case Integer t when t > 20:
System.out.println("Weather is warm");
break;
default:
System.out.println("Weather is cold");
break;
}
}
The execution flow is similar to the one described for constant pattern matching. However, there is a notable difference related to the usage of the boxed Integer
type instead of the primitive int
. This is required to perform object type matching in each case
first, followed by the relational pattern matching defined after the when
keyword. Also, compared to the switch
example with constant pattern matching, the order of the case
sections is important as they are evaluated sequentially, starting from the top one.
Pattern matching is used in this example to check the value of temperature
against specified conditions defined via relational operators. If the variable temperature
is an object of type Integer
and its value is greater than 40, the first case
is matched, and “Weather is hot” is printed. Likewise, if the variable temperature
is an object of type Integer
and its value is greater than 20, the second case
is matched, and “Weather is warm” is printed. If temperature
does not match any of the above conditions, the default case
is executed, and “Weather is cold” is printed.
As mentioned, the order of the cases is important because it determines the sequence in which they are evaluated. To understand this better, let's rewrite the previous code and swap the order of the first two cases.
public static void main(String[] args)
{
Integer temperature = // a value is assigned
switch (temperature)
{
case Integer t when t > 20:
System.out.println("Weather is warm");
break;
case Integer t when t > 40:
System.out.println("Weather is hot");
break;
default:
System.out.println("Weather is cold");
break;
}
}
When the cases are evaluated from the top, any temperature value greater than 20 will always match the first case
. This includes values greater than 40, defined in the second case
. As a result, all these temperature values will always match the first case
, making the second case
unreachable. That is a case
whose pattern was either handled already by an upper case
or is impossible to match. The Java compiler does not flag this scenario as an error; however, the C# .NET environment will report an error at compile time.
Switch with relational pattern matching in C
The following code implements the same application logic seen above in C#.
static void Main(string[] args)
{
int temperature = // a value is assigned
switch (temperature)
{
case > 40:
Console.WriteLine("Weather is hot");
break;
case > 20:
Console.WriteLine("Weather is warm");
break;
default:
Console.WriteLine("Weather is cold");
break;
}
}
The implementation of relational pattern matching in C# is similar to Java. Still, the syntax is simpler as it does not require initial type matching nor using the when
keyword to define the relational pattern. The same considerations about unreachable cases also apply to C#, with the compiler flagging them as an error if found in the code.