Introduction
Most businesses and companies using Java are still running things in either Java 7 or 8.
Since March 2018, Oracle JDK releases have been turning up every 6 months, which means two major releases per year, which means that many new features can reach production a lot quicker on the JDK side, so, more features can be pushed into the pipelines for global usage.
This also means that running things in Java 8 (which was globally released in March 2014 - that's almost 6 years ago) is slowly becoming "too" outdated, and yet, thanks to the introduction of the Streams API, it feels a lot more modern when compared to Java 7, it enables teams and developers to all agree on a more functional design of the inner working of their objects, and it was a much needed improvement of a language who is great and used worldwide, number 1 in TIOBE index, and yet, has been met with more and more critics and skepticism by users of competing technologies like Kotlin and Scala, for example.
The fact that releases now happen at a much faster pace, means that the language itself is growing and improving, striving to become more modern, more clean and listening to what all of its users would like to see added, all while taking into account previous design considerations, backwards compatibility, etc. It's definitely not an easy task! However, in recent releases, two things have been introduced in the language that will make it much more ergonomic and pleasant to use.
Local variable type inference
This feature is related to JEP 286 - Local-Variable Type Inference and JEP 323 - Local-Variable Syntax for Lambda Parameters and it allows the use of a new reserved keyword var
for performing local variable type inference.
Let's suppose we have a function that simply returns an ArrayList containing a list of names. Currently, this is how our code could look like:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> someNames = getSomeNames();
System.out.println(someNames);
}
public static List<String> getSomeNames() {
return new ArrayList<>(Arrays.asList("Ann","John","Shelby"));
}
}
So, we see that when we declare the variable someNames
inside our Main function, we need to explicitly state the type of the variable as being List<String>
. IDEs with their auto-completion features will also automatically set the type of the variable as being List<String>
.
Types are usually deduced by certain criteria that allow the compiler to "infer" the correct type, the same way when we create and use our own classes, they act as their own types, due to their behavior (i.e. their public interfaces).
In this particular example, the variable needs to have the type List<String>
because the function whose return value is being assigned to that variable is of that type.
What the JEP 286 proposes is that for local variables (and extended to lambda parameters), we can have the compiler doing type inference, i.e., deducing the type for us from the context, in such a way that we can now write:
public class Main {
public static void main(String[] args) {
var someNames = getSomeNames();
System.out.println(someNames);
}
public static List<String> getSomeNames() {
return new ArrayList<>(Arrays.asList("Ann","John","Shelby"));
}
}
Notice how we use the newly introduced var
keyword, to shorten the variable declaration.
Once we try to add a new element to the list, here's what the autocomplete in IntelliJ will show:
So, we see all the available methods are part of the List
interface which is exactly the type of the return of the function! Type inference works.
Local-Variable Syntax for Lambda Parameters
This syntax has also been extended to lambda parameters in order to allow compatibility with the local variable type inference explained earlier.
Suppose now we wanted to concatenate our list of names into a single string. For this, we can just stream and then reduce the list to a single element via a concatenation operation:
Optional<String> z = someNames.stream().reduce((a, b) -> a + b);
What the following JEP proposes is that in order to make expressions more uniform, we can now type something like this:
var z = someNames.stream().reduce((var a, var b) -> a + b);
An implicitly typed lambda expression must use var for all its formal parameters or for none of them. In addition, var is permitted only for the formal parameters of implicitly typed lambda expressions --- explicitly typed lambda expressions continue to specify manifest types for all their formal parameters, so it is not permitted for some formal parameters to have manifest types while others use var. The following examples are illegal:
(var x, y) -> x.process(y) // Cannot mix 'var' and 'no var' in implicitly typed lambda expression
(var x, int y) -> x.process(y) // Cannot mix 'var' and manifest types in explicitly typed lambda expression
So, we have explored type inference and seen how it will affect the way future code in Java can start being written.
Improved Switch
By improving the semantics and way of working of the switch
statement, Java is moving one step closer towards pattern matching which allows common logic in a program, namely the conditional extraction of components from objects, to be expressed more concisely and safely.
The JEP 325: Switch Expressions (Preview) enhancement proposal will greatly simplify how to work with the switch
statement by removing the "fall-through" behavior and allowing for support for multiple comma-separated labels in a single switch label.
In "old Java", this is how we could write a switch
statement to tell us about specific days of the week:
public static void daysOfWeek(int day){
switch (day) {
case SATURDAY:
case SUNDAY:
System.out.println("weekend");
break;
case MONDAY:
System.out.println("oh no! Monday");
break;
case FRIDAY:
case TUESDAY:
case THURSDAY:
case WEDNESDAY:
System.out.println("gotta work");
break;
}
}
We can see the break
after each print statement to avoid the fall-through behavior, and, we can see it's also quite verbose and a lot of code to read simply to do some assertions about days of the week.
In the new Java, here's how we can write this:
public static void daysOfWeek(int day){
switch (day) {
case SATURDAY, SUNDAY -> System.out.println("weekend");
case MONDAY -> System.out.println("oh no! Monday");
case TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> System.out.println("gotta work");
}
}
As we can see, we can use a new form of switch label, written "case L ->" to signify that only the code to the right of the label is to be executed if the label is matched.
This allows for a much more compact switch statement, easier to read, and closer in design to pattern matching!
We can also assign switch expressions to variables, such as:
public static String daysOfWeek(int day){
return switch (day) {
case SATURDAY, SUNDAY -> "weekend";
case MONDAY -> "oh no! Monday";
case TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "gotta work";
default -> "";};
}
Note we need a default
match arm in case a user enters an integer that is not covered by the other cases. This is very similar to pattern matching constructs like _ ->
is languages like Haskell for example.
And this is it!
Conclusion
We went over the ideas and enhancements of three Java Enhancement Proposals to see how the language keeps growing and striving to be easier to use and read, while keeping backwards compatibility with its predecessor versions.
PS: If you liked this, I can go over future JEPs!
Top comments (2)
Great post 🙌
For me one of the most wanted enhancements of java is the brand new Record Api coming in Java 14. 🤓
Yeah! I want to write about it when I can deep dive more into it