Key Takeaways
- The current design of Java’s switch statement closely follows languages such as C++ and, by default, supports fall-through semantics.
- This control flow has been useful for writing low-level code. But as switch is increasingly being used at a higher-level context, its error-prone nature has begun to outweigh its flexibility.
- As Java builders also move to support pattern matching in Java language, irregularities of the existing switch statement become impediments.
- In Java 12 a new enhancement has been made to the switch statement that has added new capabilities to it. This potentially simplifies code by extending the old switch statement and allowing it to be used as either a normal enhanced switch statement or as a “switch expression”.
- This article explores the new Java 12 switch statement and provides JShell-based examples of correct and incorrect usage.
There is no code base that is free from the use of the switch
statement; even for performance tweaks, we tend to use it over the if/else
statements. The grand old Java switch
statement was there since the birth of Java, and we have all become used to it -- and particularly its quirks.
The current design of Java’s switch statement closely follows languages such as C++ and, by default, supports fall-through semantics. This control flow has been useful for writing low-level code. However, as the switch is used in a higher-level context, its error-prone nature has begun to outweigh its flexibility.
As Java builders move to support pattern matching in the Java language, the irregularities of the existing switch statement has become an impediment. Issues include the default control flow behavior of switch blocks; default scoping of switch blocks, in which the block is treated as one single scope; and switch working only as a statement.
In Java 12 a new enhancement has been made to switch
that has added new capabilities to it, which simplifies the coding by extending the old switch statement to be used as either a normal enhanced switch statement or as a “switch expression”.
This enables both forms of “traditional” or a “simplified” scoping and control flow behavior. These changes will simplify everyday coding, and prepare the way for the use of pattern matching using the switch statement/expression in the future.
New features train
Since JDK 9 there has been a new quicker release plan introduced; with the new 6 months release cadence, the goal is that Java will become more productive and innovative, and features will be available more quickly and frequently.
We have already have seen this with each recent JDK delivered, which always brings us new Java language features alongside many API enhancements, since JDK 9, 10, 11, and up to the upcoming JDK 12 (which will be available next 19th of March 2019), and next releases.
New projects
To enable such enhancements and new features the community needs focused projects that care about a certain aspect of the language, which allows a group of java language engineers to take care of the new enhancements, rather that everything is included and developed under the giant JDK forest.
Therefore, there are many projects that have been created, and their work is focused on an individual aspect of the language. You can reach them under the project menu section at the OpenJDK.net website. To name just a few of them, we have project Panama, Valhalla, Amber, Jigsaw, Loom and many more. Our interest here is with project Amber.
Project Amber
Project Amber is sponsored by the Compiler Group, and from the sponsor name we can guess the project goal. Yes, you are correct fellow developers -- the goal of this project is to explore and incubate smaller, productivity-oriented Java language features that have been accepted as candidate JEPs under the OpenJDK JEP process.
As we can see below a list of all the JEPs and their assigned JDK if any, that the project Amber is currently either incubating or delivered them already:
JEPs currently in progress:
- JEP 302 Lambda Leftovers
- JEP 305 Pattern Matching for instance of (Preview)
- JEP 325 Switch Expressions (preview, JDK 12)
- JEP 326 Raw String Literals (preview)
- JEP draft 8209434 Concise Method Bodies
JEPs that are already delivered:
- JEP 286 Local-Variable Type Inference (var) (JDK 10)
- JEP 323 Local-Variable Syntax for Lambda Parameters (JDK 11)
Enhancing Java with Pattern matching
Pattern matching is a technique that has been adapted to many different styles of programming languages going back to the 1960s, including text-oriented languages like SNOBOL4 and AWK, functional languages like Haskell and ML, and more recently extended to object-oriented languages like Scala (and most recently, Microsoft C# language).
Project Amber added pattern matching to be embraced by the Java language. Pattern matching allows common logic in a program to conditionally extract components from objects, and for them to be expressed more concisely and safely, which will enhance the language in many constructs; for example, now it enhances the instanceof
operator (Not assigned to any JDK version yet) through JEP 305, and the switch
expressions (targeting JDK 12) through JEP 325.
Working with the new switch statement.
Since the Java SE 9 release, it has become a tradition to try out any new Java language or API feature in an interactive environment tool (REPL) JShell! This requires no IDEs nor any additional software to install -- just the JDK is enough.
Since this feature is shipped with every JDK, we can easily try a new Java SE 12 language feature, for example, the new extended switch statement and new switch expression. All we need to do is download and install the JDK 12 early access build, which can be installed from this link.
After a successful download, unzip the binaries to any location of your choice, and make the JDK binfolder accessible from anywhere in your system.
Why JShell?
I always prefer to use an interactive programming environment tool like which we have in most of the modern languages in order to quickly learn Java language syntax, explore new Java APIs and its features, and even for prototyping complex code.
This is instead of going into the tedious cycle of editing, compiling and executing code which typically involves the following process:
- Write a complete program.
- Compile it and fix any errors.
- Run the program.
- Figure out what is wrong with it.
- Edit it.
- Repeat the process.
What is JShell?
Now Java has a rich REPL(Read-Evaluate-Print-Loop) implementation with the JShell tool, referred as a Java Shell, as an interactive programming environment. So, what’s the magic here? It’s actually simple. JShell provides a fast and friendly environment that enables you to quickly explore, discover and experiment with Java language features and its extensive libraries.
Using JShell, you can enter program elements one at a time, immediately see the result, and make adjustments as needed. Rather than complete programs, in this REPL you write JShell commands and Java code snippets.
That is enough introduction for JShell, and InfoQ has recently published a thorough introduction to the tool. To deep dive and learn more about all JShell features, I have recorded an entire video training about this topic titles “Hands-on Java 10 Programming with JShell [Video]” that should help you to master the topic, and it can be reached either from Packt web site.
Starting a JShell session.
The first thing to start interacting with the tool is to start a new JShell session as in the following:
- On Microsoft Windows, just open a Command Prompt then type jshell and press
Enter
. - While on Linux, open a shell window then type jshell and press
Enter
. - And if you are working on macOS (formerly OS X), open a Terminal window, then type this command “jshell” and finally press
Enter
.
Taraaa! This command executes a new JShell session, and displays this message at the jshell>
prompt:
mohamed_taman:~$ jshell --enable-preview
| Welcome to JShell -- Version 12-ea
| For an introduction type: /help intro
jshell>
In the first line above, “Version 12-ea” indicates that you’re using Java SE JDK 12 early access. JShell precedes informational messages with vertical bars (|), and you are now ready to enter any Java code or JShell commands and snippets.
I think you have enough sharp eyes to catch the --enable-preview
option! What is this option for? Look, my friend, this option allows you to unlock any new language feature that is currently in the “preview” state, which is not officially part of the JDK yet. These features are disabled by default, as they are still in an experimental and feedback stages.
Using this flag allows you as a developer to try preview features and provide your feedback for more improvements, or even feedback that you dislike it or any part of it. In this article, we are exploring the new switch expressions feature, and this is why we need to include this option while starting a JShell session.
Note: Since this is a preview feature, you still have some time to give your feedback to the Amber mailing list.
That is enough theory for now, and let’s try some fun with the new switch statements/expression snippets to clear up the concepts and learn how to use this great new feature.
Hacking switch statement/expression
First, we need to make sure we know about our present; and where we are today with the current edition of the switch
statement and even its quirks that we used to do in our everyday coding tasks.
Let’s consider a simple method that takes a Day
as a variable; the method switches over the Day
enum which contains normal days (Sat, Sun, Mon...
etc.), and based on that it returns either it is a “weekend” or a “working day”.
In the Jshell session we already started before, Let’s define the Day enum
as the following:
jshell> enum Day{
...> NONE,
...> SAT,
...> SUN,
...> MON,
...> TUS,
...> WED,
...> THU,
...> FRI;
...> }
| created enum Day
Now let’s define our method String whatIsToday (Day day)
to switch (we use the normal switch) over day
value and return
either it is working or weekend day, like the following:
jshell> String whatIsToday(Day day){
...> var today = "";
...> switch(day){
...> case SAT:
...> case SUN: today = "Weekend day";
...> break;
...> case MON:
...> case TUS:
...> case WED:
...> case THU:
...> case FRI: today = "Working day";
...> break;
...> default: today = "N/A";
...> }
...> return today;
...> }
| created method whatIsToday(Day)
jshell> whatIsToday(Day.SUN)
$3 ==> "Weekend day"
Although this function works perfectly, and if we used the original version with the many break statements for each case, it introduces visual noise which often makes it hard to debug the errors. It also makes the code unnecessarily verbose, where mistakenly missing a break statement means that an accidental fall-through occurs.
In the above example we have even reduced the use of breaks by using fall-through mechanism for common days that have the same values, but it still very lengthy.
Traditional switch statement enhancements
Thanks to JEP 325, which proposed a new “arrow” (“switch labeled rule”) form, which is written as "case L ->" to indicate that only the code to the right of the label is to be executed if the label is matched, and can be used with a switch
statement or with a switch
expression.
Similarly, the traditional "colon" syntax ("switch labeled statement group") can also be used with both cases too.
Despite that both colon and the new arrow syntax can be used with both cases, so the presence of the colon (:) does NOT necessarily imply a switch statement and the presence of an "arrow" (->) does NOT necessarily imply a switch expression.
Now let’s translate the previous theoretical statements into action, for example, the previous method String whatIsToday(Day day)
can now be rewritten as the following simplified version:
jshell> /edit whatIsToday
By running the previous JShell /edit
command, it will open a JShell edit pad for easier modification of large code snippets such as this method, so in JShell edit pad modifies the method as the following:
String whatIsToday(Day day){
var today = "";
switch(day){
case SAT, SUN -> today = "Weekend day";
case MON, TUS, WED, THU, FRI -> today = "Working day";
default -> throw new IllegalArgumentException("Invalid day: " + day.name());
}
return today;
}
When you finish your modifications, click the Exit
button to accept the modifications and return back to the current JShell>
session, to try the method again with some values:
jshell> /edit whatIsToday
| modified method whatIsToday(Day)
jshell> whatIsToday(Day.SUN)
$5 ==> "Weekend day"
jshell> whatIsToday(Day.MON)
$6 ==> "Working day"
jshell> whatIsToday(Day.NONE)
| Exception java.lang.IllegalArgumentException: Invalid day: NONE
| at whatIsToday (#11:6)
| at (#12:1)
jshell>
If you scrutinize the previous new switch statement structure, you should notice many changes here, first, it is more clear, concise, and “breakless”, isn’t it?
Second, you notice that the switch statement takes advantage of the new "arrow" syntax ("label rules") form to accomplish its switching without explicit specification of the break
, which saves us from the often-dreaded switch "fall-through" case.
It is worth to note that the code to the right of a "case L ->
" switch label is restricted to be an expression, a block, or (for convenience) a throw
statement, as we have done in the previous method for the default
case of the switch statement to identifies invalid day, by throwing an IllegalArgumentException
.
Multiple comma-separated labels in a single switch case
Traditionally in the old switch
statement, when we need to identify the multiple cases that should execute the same set of statements, we use the fall-through mechanism.
But now the third notice here, is that we have used the new “multiple comma-separated labels in a single switch case” structure, by just using a comma to separate the common cases that should execute the same statement.
Switch labeled statement group
As per JEP 325, the traditional "colon" syntax ("switch labeled statement group") can also be used normally and we can rewrite the previous method again in this colon form as the following:
String whatIsToday(Day day){
var today = "";
switch(day){
case SAT, SUN: today = "Weekend day"; break;
case MON, TUS, WED, THU, FRI: today = "Working day"; break;
default: throw new IllegalArgumentException("Invalid day: " + day.name());
}
return today;
}
I will leave this as a small exercise for you. You can try it yourself, by running the JShell command /edit whatIsToday
and replacing the current method with arrow token syntax, with the previous method with colon/break syntax, and try it with some values, and it should work like a charm.
New switch expression
Before we deep dive into this, you may wonder what was the switch
was like previously before it started to act as an expression? Before Java SE 12, the switch was always a statement; which is an imperative construct that directs control flow and can never be the destination. On the other hand, an expression always gets evaluated to exactly a value. Because the ultimate goal of any computation is a result, a value can be assigned to a destination.
Now that we have explored the difference between the statement and expression, let’s see how the new switch expression works in action. Actually, many existing switch
statements that we use in our daily code, and even the above code, are essentially a simulation of a switch
expression, where each arm either assigns to a common target variable or returns a value.
Now we can make use of the new switch
expression to return the value directly to be assigned to the target variable, let’s modifies the StringwhatIsToday(Day day)
method:
String whatIsToday(Day day){
var today = switch(day){
case SAT, SUN -> "Weekend day";
case MON, TUS, WED, THU, FRI -> "Working day";
default -> throw new IllegalArgumentException("Invalid day: " + day.name());
};
return today;
}
Taking a deep look at the modified method, we can note that we wrote the switch statement as an expression: first, the switch is written after the equal sign; second since it’s part of a statement, it needs to end with a semicolon, whereas the classic switch statement doesn’t; and third, what comes after the arrow token is the return value.
As I have mentioned previously, we can use the traditional “colon” syntax “case L:
” within the new switch expression, and so we can rewrite the previous method using the break with value statement as the following:
String whatIsToday(Day day){
var today = switch(day){
case SAT, SUN: break "Weekend day";
case MON, TUS, WED, THU, FRI: break "Working day";
default: throw new IllegalArgumentException("Invalid day: " + day.name());
};
return today;
}
The two forms of break
(with and without value) are analogous to the two forms of return
in methods.
Statement blocks
While we use the new switch
statement/expression in our daily tasks with either “colon” or “arrow” syntax; we will often use in most of the cases a single expression to the right hand of any of both forms.
However, there are cases when we need to evaluate multiple statements at the same time. And easily we can do this with blocks {}, also it is also very handy that we can create and use the same variable name inside a single switch statement/expression many times as the following:
String whatIsToday(Day day){
var today = switch(day){
case SAT, SUN: break "Weekend day";
case MON, TUS, WED, THU, FRI:{
var kind = "Working day";
break kind;
}
default: {
var kind = day.name();
System.out.println(kind);
throw new IllegalArgumentException("Invalid day: " + kind);
}
};
return today;
}
It is a poly expression
A switch
expression is a “poly expression”; if the target type is known, this type is pushed down into each case arm; if not, a standalone type is computed by combining the types of each case arm.
Consider this switch expression assignment:
jshell> var day = Day.SUN
day ==> SUN
jshell> var today = switch(day){
...> case SAT, SUN -> "Weekend day";
...> case MON, TUS, WED, THU, FRI -> "Working day";
...> default -> {
...> var len = day.name().length();
...> break len;
...> }
...> };
today ==> "Weekend day"
jshell> var day = Day.NONE
day ==> NONE
jshell> var today = switch(day){
...> case SAT, SUN -> "Weekend day";
...> case MON, TUS, WED, THU, FRI -> "Working day";
...> default -> {
...> var len = day.name().length();
...> break len;
...> }
...> };
4
today ==> 4
If we take a step closer and analyze the switch expression above, we can find that we have two arms returning a String
, and default arm returning an int
variable len
. Both works perfectly as the target destination is var
.
Now let’s declare explicitly that the destination assignment to be of type int
instead of var
and this will make the compiler complains, to satisfies this condition “if the target type is known, this type is pushed down into each case arm”:
jshell> int today = switch(day){
...> case SAT, SUN -> "Weekend day";
...> case MON, TUS, WED, THU, FRI -> "Working day";
...> default -> {
...> var len = day.name().length();
...> System.out.println(len);
...> break len;
...> }
...> };
| Error:
| incompatible types: bad type in switch expression
| java.lang.String cannot be converted to int
| case SAT, SUN -> "Weekend day";
| ^-----------^
| Error:
| incompatible types: bad type in switch expression
| java.lang.String cannot be converted to int
| case MON, TUS, WED, THU, FRI -> "Working day";
| ^-----------^
What can't be done with new switch feature
Let’s now explore how we can make the compiler angry and complain about the switch statements/expression code that we write, and accordingly, learn how to try to avoid this to make compiler happy :).
break cannot return a value within a switch statement
It is a compilation-time error if you attempt to have a break
associated with a switch
statement to return a value:
jshell> String whatIsToday(Day day){
...> var today = "";
...> switch(day){
...> case SAT, SUN: break "Weekend day";
...> case MON, TUS, WED, THU, FRI: break "Working day";
...> default: throw new IllegalArgumentException("Invalid day: " + day.name());
...> }
...> return today;
...> }
| Error:
| unexpected value break
| case SAT, SUN: break "Weekend day";
| ^------------------^
| Error:
| unexpected value break
| case MON, TUS, WED, THU, FRI: break "Working day";
| ^------------------^
| Error:
| unreachable statement
| return today;
| ^-----------^
jshell>
Arrow syntax point only to a statement within switch Statement
Again, it is a compilation-time error if you try to use arrow syntax with a switch statement to return a value. In this case, the arrow syntax should only point to a statement and NOT returning a value:
jshell> String whatIsToday(Day day){
...> var today = "";
...> switch(day){
...> case SAT, SUN -> "Weekend day";
...> case MON, TUS, WED, THU, FRI -> "Working day";
...> default -> throw new IllegalArgumentException("Invalid day: " + day.name());
...> }
...> return today;
...> }
| Error:
| not a statement
| case SAT, SUN -> "Weekend day";
| ^-----------^
| Error:
| not a statement
| case MON, TUS, WED, THU, FRI -> "Working day";
Arrow and colon/break syntax mixing is not allowed
The compiler will be very angry and complain if you try mixing between the "arrow" and the traditional colon/break syntax:
jshell> String whatIsToday(Day day){
...> var today = "";
...> switch(day){
...> case SAT, SUN -> today = "Weekend day";
...> case MON, TUS, WED, THU, FRI: today = "Working day";break;
...> default -> throw new IllegalArgumentException("Invalid day: " + day.name());
...> }
...> return today;
...> }
| Error:
| different case kinds used in the switch
| case MON, TUS, WED, THU, FRI: today = "Working day";break;
| ^--------------------------------------------------------^
All match cases should be covered in a switch expression
Finally, it is a compiler-time error again if all switch cases are not covered within a switch
expression for a specific test variable. If you don’t cover all of them you will run into the following error:
jshell> String whatIsToday(Day day){
...> var today = switch(day){
...> case SAT, SUN -> "Weekend day";
...> case MON, TUS, WED, THU, FRI -> "Working day";
...> };
...> return today;
...> }
| Error:
| the switch expression does not cover all possible input values
| var today = switch(day){
| ^-----------...
Covering all the cases within a switch expression is not an easy task and irritated for sure, the fix here is trivial and small -- just add the default
case and it will compile like a charm. I will leave it for you to make the above code compiles successfully.
Future of Switch
As with the old switch
, both the new enhanced switch statement and switch expression work fine with enums. So, what about other types? Both new enhanced “traditional” and “expression” forms are alike, they can also switch over a String, int, short, byte, char
, and their wrapper types; so far nothing has changed here.
Extending the switch in future Java versions is a goal, for example, to allow switching over float, double
, and long
(with their box types), which was disallowed before.
Conclusion
In this article, you have learned about Project Amber, pattern matching, and how JEP 325 has extended the switch
statement so that it can be used as either a statement or an expression, and that both forms can use either a "traditional" or "simplified" scoping and control flow behavior.
These changes will also simplify everyday coding, and it can save you from bugs caused by “fall-through” issues where you forget to add a relevant break statement. I have also listed the most common mistakes a developer could make when starting to use the new switch statement/expression.
Furthermore, this language change prepares the way for the use of pattern matching (JEP 305) in switch statements/expression, and also extends the current switch to support switching over more primitive types like float, double, and long, alongside their wrapper types.
We have reached the end of our article, it was a pleasure writing it, and I hope you enjoyed reading it! If your answer is yes, then please click the “like” button and spread the word via your friends and social media!
Resources
- Project Amber
- Pattern Matching for Java
- JEP 325: Switch Expressions
- JEP 305: Pattern Matching for instanceof
- Hands-on Java 10 Programming with JShell
- Getting Started with Clean Code Java SE 9
About the Author
Mohamed Taman is Sr. Enterprise Architect @Comtrade digital services, a Java Champions, An Oracle Groundbreaker Ambassador, Adopts Java SE.next(), JakartaEE.next(), a JCP member, Was JCP Executive Committee member, JSR 354, 363 & 373 Expert Group member, EGJUG leader, Oracle Egypt Architects Club board member, speaks Java, love Mobile, Big Data, Cloud, Blockchain, DevOps. An International speaker, books & videos author of "JavaFX essentials", "Getting Started with Clean Code, Java SE 9", and "Hands-On Java 10 Programming with JShell", and a new book “Secrets of a Java Champions”, Won Duke’s choice 2015, 2014 awards, and JCP outstanding adopt-a-jar participant 2013 awards.