Java 12 — New Language Features
The only new feature, that the Java 12 language has introduced, is the Switch Expressions (JEP 325) (Preview Feature) (that was soon superseded by JEP 354, as a Second Preview targeted for JDK 13, and finally coined into JDK 14 as a result of JEP 361) — a new, enhanced and extended switch
statement, enhancement and extension of which can be summarised in these three points:
- New, simplified form of switch label, written as “
case label ->
”, that changes how your code controls the execution flow, as with this new form of case label definition, the fall-through semantics is no longer exercised, which means - if the label matches, only the code right of the label is executed and control does not fall through even whenbreak
is not present. Note, that both -switch
statements and expressions can now use traditional, or simplified/new control flow, with traditional, or new switch labels, respectively; switch
is not anymore only statement, since Java 12, it can also be a valid expression, which means, it can return/produce/yield a value (more about this, a bit later). So,switch
, since Java 12, is either a statement or an expression;- Pleasant scoping with newer switch label.
What was the idea behind this enhancement?
Idea was to:
simplify everyday coding, and also prepare the way for the use of pattern matching (JEP 305, targeted for JDK 14) in
switch
.
— OpenJDK.
Let’s start with beginning and build up consecutively.
New switch labels
Before version 12, Java’s switch
semantics was quite similar to how it is implemented in C and C++, that is - having a fall-through semantics by default (matched case executes and flow falls through until break
is encountered).
I hope you can identify the problem here:
int month = 1;switch (month) {
case 1:
System.out.println("January");
case 2:
System.out.println("February");
//...
case 12:
System.out.println("December");
default:
System.out.println(month + " is not a valid month.");
}
if not, probably it’s better to familiarise yourself with the basics of switch statement (little spoiler alert… all the println
methods are executed).
While this traditional and popular fall-through control flow can often be useful, switch
is, still, in most of the cases, used as a high-level programming facility, and therefore, it still can be stylistically error-prone and even just boring fashion, to always be dependent on such a repetitive, verbose and ugly clutter as break
statements, as in this case, breaking properly and consistently, is the only salvation you must be always bearing in mind, if you want to avoid unexpected bugs and misbehaviour. That’s even worse in the case of long-enough switch statement blocks.
Verbose, WET, repetitive and cluttered code, especially when it’s unnecessary (if your code “smells”, chances are really high smell is not necessary to be present, and it’s there, because you haven’t used, or you misused, or you malformed the pattern, because you weren’t careful, weren’t pedantic, weren’t patient, or just didn’t care about code — which is truly bad practice! yeas, I advocate clean-code), makes noise, both — into others’ heads, and into yours, alike. Such dirty and noisy places are often sources of errors, that sometimes are hard to trace.
So, a newer form for switch
label has been introduced in Java SE 12, and its syntax looks as “case label ->
". If this syntax is used, only the code that stands on the right side of label is executed (of course, IFF the label is matched), and execution flow does not fall down through the block of the switch
statement.
We can therefore rewrite previous code example as follows:
int month = 1;switch (month) {
case 1 -> System.out.println("January");
case 2 -> System.out.println("February");
//...
case 12 -> System.out.println("December");
default -> System.out.println(month + "is not a valid month.");
}
Way cleaner, much more readable and elegant code. Wouldn’t you agree? sure, you would.
Few important notes about new switch labels:
- multi-case labels are supported, as:
case 6, 7, 8 -> System.out.println("Summer time is here!");
2. code that goes to the right of case L ->
label, is restricted to be a single [expression] statement, a {block of code}, or a throw
statement;
3. break;
is not allowed as a single statement, as it is implicit semantics.
If you want to have a case, that, when matched, causes nothing, you can define it as case label -> {}
. This is similar to older case label: break;
.
Remember, that with the older switch labels, you could have had a break;
as the only statement, after case Label:
and this was permitted, because, you might wanted to just break out from the switch block for particular value(s);
Explicit break;
, however, is allowed in the block that follows the label case L ->
, but that doesn’t make much sense, because fall-through is, still, implicitly disabled with new switch labels;
4. You cannot mix different kinds of switch labels (like case L:
and case L ->
) in one switch statement or switch expression. If you will, you will get a compiler error:
different case kinds used in the switch
5. Both — switch
statements and expressions can now use either old, or a new switch label (that is either old, or a new control flow, respectively).
Major takeaway:
New switch label (“case label ->”) facilitates better execution flow control and enables cleaner, more intuitive syntax, as after label is matched, no falling-through happens, and switch statement is completed even without “break;” statement.
What’s next?
I’ve said switch
can now be an expression, right?
— right.
So, what does that really mean?
Switch Expressions
You may find out, that expressing multi-way conditionals as expressions rather than statements, often seems more natural.
Whether it’s if-else
or a switch
statement, it’s still a statement, and no, don’t think it’s anything bad, everything is OK with statements. However, when it comes to evaluating some value (like computing expression and returning it, returning the value from a method, etc.), it seems much more intuitive and natural to get those values from expressions, rather than execute statements which return/assign the values.
Many switch
statements can be more boiler-plate, verbose and hard-to-read versions, than their expression alternatives.
Imagine you declare a variable, which you want to initialise by switch
statement, that receives an input, matches it to the particular label, and code that follows matched label, eventually, initialises your variable.
So, something like this:
int x = 1;
String season;switch (x) {
case 12:
case 1:
case 2:
season = "Winter";
break;
case 3:
case 4:
case 5:
season = "Spring";
break;
case 6:
case 7:
case 8:
season = "Summer";
break;
case 9:
case 10:
case 11:
season = "Autumn";
break;
default:
season = "invalid month number";
break;
}System.out.println(season);
This is verbose, cluttered, repetitive, and therefore - error-prone.
Rewriting this switch statement with the new switch label, as:
int x = 1;
String season;switch (x) {
case 12, 1, 2 -> season = "Winter";
case 3, 4, 5 -> season = "Spring";
case 6, 7, 8 -> season = "Summer";
case 9, 10, 11 -> season = "Autumn";
default -> season = "invalid month number";
}System.out.println(season);
is much better, but still it has a bit more ceremony than the switch expression, which we can write for the same bahviour:
int x = 1;
String season = switch (x) { //expression! this yields value!
case 12, 1, 2 -> "Winter";
case 3, 4, 5 -> "Spring";
case 6, 7, 8 -> "Summer";
case 9, 10, 11 -> "Autumn";
default -> "invalid month number";
};System.out.println(month);
and pay attention, that this is an expression, a switch expression, which means, it evaluates expression of the matched label and returns/yields respective value. So, this switch expression will assign the value (that it produces from calculating expression), according to whichever label is matched to the input x
.
Important citation:
extending
switch
to support expressions raises some additional needs, such as extending flow analysis (an expression must always compute a value or complete abruptly), and allowing some case arms of aswitch
expression to throw an exception rather than yield a value.
— JEP 325.
For a lot of people, this citation can be quite blurry and unclear, especially when complete abruptly, I think, is an ambiguous and unclear wording used in JEP page, because JLS defines what does Abrupt Completion of Statement is, JEP 325 says, that switch expression may complete abruptly, but return
does not work and only break
can yield a value (I would appreciate if someone from JLS and/or JEP comment on this).
To demystify above citation, here is what it really means:
For all possible input arguments (that means you cover all the possible input scenarios, by either having adefault
case, or covering all the cases of enum constants (compiler understands and lets you omit default case, if you swith on enum instance and cover all the constants of that enum) otherwise, your switch
expression might not have a matching case), switch expression must either (1) compute and return/yield the value, or (2) throw an exception.
Few notes:
- if after
->
syntax, {code block} follows, thenbreak
followed by value expression must be used to return/yield the value. Yes,break
statement is overloaded here, which, later, was correctly suggested to be very confusing, and later JDK releases changed this (see my article on Java 13 language enhancements); switch
expressions must be terminated by semicolon, as they are expressions.
Switch expressions with old switch labels
Like switch
statement, switch
expression can also use a “traditional” switch label case Label:
(implying fall-through semantics). In this case values would be yielded using the break
with value statement (in both cases, whether statement follows the case immediately, or it’s enclosed in the {block}).
If we’ll rewrite above switch expression with traditional, old switch labels, instead of new form ones (->
), we’ll get:
String season = switch (x) { //expression, with old switch labels
case 12:
case 1:
case 2:
break "Winter";
case 3:
case 4:
case 5:
break "Spring";
case 6:
case 7:
case 8:
break "Summer";
case 9:
case 10:
case 11:
break "Autumn";
default:
break "Wrong number";
};
System.out.println(season);
Default scoping of switch blocks - pleasant characteristic of the new switch label
When using older switch case, entire switch block is in one scope, and unless you explicitly create a {block of code}, next to the label, and declare variable in that block, your variable, declared after particular case, will be scoped to the entire switch block.
Again, you can still create case Label: {block}
and declare variable in the {block}, and in this case, it’s going to be locally scoped, but if you just have series of statements following the switch
case, you won’t be able to declare
variables with same names.
Maybe this is not the big deal, but let me still tell you, when you use a newer form of switch label, as the code to the right of a “case L ->
" switch label is restricted to be an expression, a block, or a throw
statement, no more clashing of the local variables, in the same scope of switch
, will occur.
— Why? how so?
Whenever a switch
arm (that’s sometimes how we refer to a particular section of a case
) wants to introduce a local variable, it has to do that in the block, and otherwise is not possible (due to restriction on what can follow the new switch label form).
This, in turn, results in a convenience of avoiding clashes of two or more variables in the same scope.
That’s it for now.
I really tried to give my best on this topic, and I really hope you enjoyed this reading.
What I hope for, also, is that you took away something important, you’ve learnt new things and, maybe, you’ve got excited.
Thank you for spending the time, reading my work and learning with it.
I’m a very newbie in the world of blogposts, so, consider following me and thus — encouraging me for writing more articles (posts, or whatever you call it) like this.
Like always: any constructive feedback and/or suggestion is more than welcome.
Giorgi Tsiklauri