Java 8 has brought us a bunch of new language features, among which are Lambda Expressions and the Stream API. The intention of these features is to provide us with a way of expressing frequently used functionality in a cleaner and more concise way. While there are certainly many situations in which these features excel, their usage does not necessarily guarantee that the code is cleaner and easier to read compared to writing it the »old-style way«.
One such questionable situation came up during one of our quality-control projects with one of our customers. Once a month we meet with the developers to discuss the recent improvements and setbacks in the quality of their code. Being generally up to speed with Java and its features, it came at no surprise, that the developers starting using the new features right away. During the latest meeting, one piece of code stimulated a lot of discussion about whether the usage of the new Java 8 features makes the code more readable or not.
Let me first show you the piece of code that we were discussing. I modified the code a little, such that no customer information is disclosed. The purpose of the code is to do a certain computation
n times in parallel. Each computation returns an object of type
Result, which is stored wrapped in a
Future object and stored in a list. Of course the code does not »make any sense« without knowing the actual computation. However, it is still useful to illustrate the problem. Now, here is the version that we found in the customer’s code, which uses Java 8 features (
executor is of type
As you can see the code uses the new Stream API to obtain the integers, a lambda expression to create the
Future objects from the integers, another lambda expression to implement the
Callable parameter passed to
executor.submit and a Collector to finally create a list from the stream. Now compare this to a version that does not use Java 8 features:
Now the important question: Which of the two is more readable? More maintainable? Easier to change? Less error-prone? The first version is obviously shorter. However, it uses method chaining and nested lambda expressions both of which complicate the understanding. The second version is dominated by the circumstantial in-place implementation of the
Callable. Another weakness is the final variable
j, which is needed to use the value of
i in the
call method. Apart from that the version uses only basic features. In particular the
for loop is probably closer to what one would expect for iterating through a sequence of integers.
After thinking some time about both versions, I found the best version to be a combination of both. The following version uses the traditional
for loop to do the iteration, but replaces the circumstantial implementation of the
Callable with a lambda expression. The only imperfection that remains is the need for the variable
After all, I am sure that it will be hard to find a general agreement on whether code with lambda expressions is more readable than old-style Java code. It does certainly depend on the specific code snippet. Furthermore, each of us has his own idea about which type of code is more readable. However, one thing is certain: Some developers will use the new features, others will not and yet others will use them from time to time. The problem is a growing inconsistency of how similar problems are solved in the code. Sooner or later, developers will come across code that does not match their idea of readable code and will have problems understanding and changing it. The larger the number of available features, the higher the inconsistency and heterogeneity of the code.
To mitigate this problem, you should first make sure that each developer knows the range of available features, how they can be used to solve recurring problems and whether they have any implications. Even if they do not use the features themselves, the developers have to know how they work to be able to understand code written by others. You should also include information about when and how to use certain language features in your coding guidelines. This helps to ensure that recurring problems are solved consistently. Finally, you should use a static analysis tool like Teamscale to continuously check for violations of the guidelines and anti-patterns in the usage of language features.
Our latest related blog posts.