The release cycle of Java has changed quite dramatically recently, meaning we're getting new features at a more rapid pace than earlier. If you still hadn't had the time to read up on what's been going on the last releases, look no further!
We've previously covered what's new in Java 11, and today I want to tell you a little bit about what has happened since then. Please beware that none of these versions are LTS (long term support) versions. So if you're not absolutely sure you'll be able to upgrade every six months until Java 17 is released, you might want to stay put on Java 11 for your most critical applications.
With that disclaimer out of the way, we can jump into the good stuff. I wont cover all new features (have a look at the JEP list for version 12, 13 and 14 if you'd like), but I'll highlight a few interesting language features.
Today I'm focused on two new features, another post with some additional features will be available in a couple of days.
Switch enhancements
switch
has gotten some changes! They have been previewed in JDK 12 and again in 13 and are at time of writing aimed for release in JDK 14. We'll be getting one new feature for when switch
is used as a statement and, in addition, it can also be used as an expression going forward.
Arrow labels
We can now use arrow labels (case X ->
) in addition to the old case X:
label in switch
statements. Upon using arrow labels, only the expression or statement on the right hand side of the arrow will be executed. This means there will be no fall through so you no longer need to remember break;
:
String quantityString; switch (n) { case 1 -> quantityString = "one"; case 2 -> quantityString = "two"; default -> quantityString = "many"; }
Switch expressions
switch
can now be used as expressions, i.e. it can return a value. This means we won't have to first declare a variable, then assign a value in every single branch. Combined with arrow labels it allows us to express our intent in much fewer lines of code:
String quantityString = switch (k) { case 1 -> "one"; case 2 -> "two"; default -> "many"; };
Sometimes you might be forced to execute a code block as part of a case expression. In order to combine this with arrow label syntax, you must yield
a value at the end of the block:
DayType type = switch (day) { case 1, 2, 3, 4, 5 -> WEEKDAY; case 6, 7 -> WEEKEND; default -> { logger.warn(day + " is not a valid day. Legal values are [1..7]"); yield UNKNOWN; } };
You may also turn switch
es using the old style labels from statements to expressions by using yield
.
Type type = switch (day) { case 1, 2, 3, 4, 5: yield WEEKDAY; case 6, 7: yield WEEKEND; default: logger.warn(day + " is not a valid day."); yield UNKNOWN; };
⚠️ Please remember that it is the arrow label syntax that prevents fall through. That means, if you forget to yield
, the next case expression will be evaluated and you might end up with the wrong result. I'd recommend you stay away from this syntax for this reason.
As usual more features leads to more complexity. We've certainly gotten a more feature rich switch
, but as illustrated above it has also become much more complex. You have to remember which label style, :
or ->
, has fall through – and what was the point of yield
again? Stephen Colebourne (you know, that guy who wrote Joda-Time) has written a much more in depth blog post where he raises some important questions regarding the UX of these features. If you're interested in the new switch
features, make sure to read this first!
Pattern matching for instanceof
As a Java developer you've most likely been in a situation where you have to check if an object is a certain type, and if it is – cast it to that type. This pattern is widely used in e.g. equals
implementations.
Introduced as a preview feature in JDK 14, instanceof
is extended to take what's called a type test pattern instead of just a type. A type test pattern consists of a predicate and a binding variable.
⚠️ Disclaimer: This feature is currently not supported by IntelliJ. You can follow the progress in Jetbrain's issue tracker.
Consider the following example:
@Override public boolean equals(Object obj) { if (obj instanceof Person) { Person other = (Person) obj; return this.name == other.name; } return false; }
Using this new feature, you may rewrite as follows:
@Override public boolean equals(Object obj) { if (obj instanceof Person other) { return this.name == other.name; } return false; }
In the above example Person other
is the type test pattern.
Inside the if
block, you may use other
and it's guaranteed to be a Person
. other
is, however, not accessible outside the if
block.
⚠️ It's worth noting that the scope of a binding variable is determined by the semantics of the containing expressions, which might lead to some surprising results:
if (!(obj instanceof String s)) { System.out.println(s); // 1 } else { System.out.println(s); // 2 }
obj
is not aString
,s
is some other variable in scope (e.g. a member of the enclosing class).obj
is aString
,s
isobj
cast toString
.
This is certainly not the most game changing feature in itself, but it sends a signal that Java is embracing pattern matching as a concept, a language feature many other popular languages has supported for a long time. And as an added bonus, your equals
implementation can be shortened slightly to emphasize the important bits.
Enabling preview features
The new switch
changes currently have preview status in the most recently released JDK version (13). This means you probably don't want to use them to a large extent in the code base you earn your living of. APIs could change, and there is still a chance the feature won't be promoted to a stable feature in its current form.
It is, however, valuable to experiment with them. Trying new features is a good way to broaden your skill set, and if there is something you strongly dislike about the usability of a feature you can even provide feedback to the JDK developers. Look for the Discussion label on a feature's JEP page (linked below) to find the correct mailing list.
To get started experimenting with preview features in the tool you use, follow the guides below:
Maven
To activate during compilation, add the following configuration to maven-compiler-plugin
:
<configuration> <release>13</release> <compilerArgs> --enable-preview </compilerArgs> </configuration>
For test execution, add the following configuration to maven-surefire-plugin
and/or maven-failsafe-plugin
:
<configuration> <argLine>--enable-preview</argLine> </configuration>
Gradle
Enable the compiler flag for compilation and test execution as follows:
compileJava { options.compilerArgs += ["--enable-preview"] } test { jvmArgs '--enable-preview' }
IntelliJ
Go to Project Settings > Project and find the Project language level dropdown. If you previously targeted version 13 and want to enable the preview features, choose 13 (Preview).