Last updated at Mon, 06 Nov 2017 17:45:00 GMT
Java 8 introduced a host of new features, including lambda functions and streams. In this article I will focus on these two features as they are the most impactful features that were added in this new version of Java.
Lambda Functions
If you ever had to write a GUI for an application with an OO language you’ll understand the pain of writing event handlers, such as a mouse click event. So much boilerplate needed because you had to define a disgusting inline class with a single method. With the release of Java 8 the lambda function was introduced. This means you can write functions inline without having to define a class. With the mouse click example, you would have to write something like this.
Java 7:
button.onMouseClick(new Eventhandler() { @Override public void handle(MouseEvent e) { //handler code goes here } });
In Java 8, the code is greatly simplified by using a lambda function:
button.onMouseClick( mouseEvent -> { //handler code goes here });
Imperative vs Functional
Working with lists in Java can sometimes be a pain. Unlike languages such as python, sometimes it feels like you’re writing verbose code when using a loop. Take, for example, filtering a list of exam grades based on certain criteria.
The following two programs illustrate the difference between the imperative approach and the functional approach.
Java 7:
ArrayList results = new ArrayList(); for (Grade grade: grades) { if (grade.getScore() > 40 && grade.getSubject() == Subjects.SCIENCE) { results.add(grade.getScore()); } } System.out.println(results);
This code iterates through each grade score and adds to a new list of grades. Each score, that doesn’t fall below the fail threshold, and whose subject is Science, is added to a new list.
With the new java streaming api, a functional approach can be adopted in order to avoid the ugly nested statements.
Java 8:
ArrayList results = grades.stream() .filter(grade -> grade.getScore() > 40) .filter(grade -> grade.getSubject() == Subject.SCIENCE) .collect(Collectors.toList()); System.out.println(grades);
The java 8 code first converts the List of Grades to a stream of Grades using the stream() method. Then, the fail grades are filtered out by passing a lambda function to the filter method, which checks if the score is above the fail threshold. This produces a stream of Grades containing only grades with a passing score.
Similarly, another filter method keeps all items whose subject is Science. It operates on the stream produced by the previous filter method.
Finally, the collect method takes a stream and produces an end result. It’s known as a terminal function, because it ends the stream chain. In this case it simply converts the stream to a List.
In comparison to using an imperative approach, the code is much neater. The problem of nested conditionals is removed by using method chaining. This is possible because each stream operation (except the terminal operation) returns another stream, which another stream method can be applied to.
You might be thinking that it’s not such a big deal to be able to change your programming style from imperative to functional. However there’s more to streams than simply code readability.
Multithreading with Streams API
I believe that the traditional approach to writing java programs is outdated. It’s severely lacking in an age where the average laptop has four cpu cores. The power of the remaining cores isn’t always leveraged by java programmers because of the difficulty in writing multithreaded programs. A simple mistake can lead to a non-deterministic program that is a nightmare to debug. However, the streams API comes to the rescue.
The Streams API has what the developers like to call “parallelism almost for free”. It provides a method called parallelStream() which can be used instead of the stream() method. Making this replacement in our grades example gives us:
List grades = gradeScores.parallelStream() .filter((Grade grade) -> grade.getScore() > 40) .filter((Grade grade) -> grade.getSubject() == Subject.SCIENCE) .collect(Collectors.toList());
This does all the hard work for you and performs the operations in parallel, which saves you the sweat and tears of writing and debugging your own multithreaded program.
Java still needs work
While these new features have greatly enhanced the readability and efficiency of Java programs, there is still some work to be done. Here are some features I would like to see in the next version of Java.
Extension methods
Extension methods still haven’t been implemented in Java. The above example could be improved by writing extension methods Grade.isPassGrade
and Grade.isSubject(Subject)
. However, this can’t be done if you don’t have access to the source code and so you must write a separate static method or a lambda function. A workaround to this is to write a subclass of Grade and add the methods there. This is not the same though, because references to Grade have to be changed to this new subclass, which could be painful to refactor.
Collection Literals
Another area in which Java is still lacking is with initialization of collections. To get a list of 1 to 5, you need to use Arrays.asList(1, 2, 3 ,4 ,5)
, whereas with a language like python you can simply write [1, 2, 3, 4, 5]
.