Introduction
Laziness is one of the three great virtues of any programmer according to Larry Wall, author of the book Programming Perl. Consequently, it is no surprise that Mario Fusco, senior software engineer at Red Hat, highlighted the power of lazy evaluation in Java, which represents an interesting approach for code optimisation. The two ideas are in fact related: lazy programmers tend to delay things until they are really needed; and lazy evaluation consists of delaying the evaluation of an expression until its value is needed.
Mario Fusco was invited to deliver a speech at Codemotion Milan 2018, and he focused on how to apply lazy evaluation in Java. In fact, Java methods are strict, since they evaluate their arguments as soon as they are passed to them. However, even if Java is mainly a strict language, there are some exceptions. For instance, boolean operators &&
and ||
are lazy, since the second operand is not evaluated, the first is deemed sufficient for computing the resulting boolean value. The same considerations apply to if-else
, ternary operator ?
:, for
/while
loops, and the Java 8 streams.
Functional Programming
In general, we can exploit the support to functional programming in order to turn Java into a lazy language. To explain how, Mario Fusco started from a simple example, which re-implements the ternary operator:
<T> T ternary(boolean pred, T first, T second) { if (pred) { return first; } else { return second; } } String val1() { return "first"; } String val2() { return "second"; } String result1 = bool ? val1() : val2(); String result2 = ternary(bool, val1(), val2());
The above code snippet is strict: indeed, in order to execute the ternary
method, the arguments val1()
and val2()
need to be both evaluated. Consequently, we cannot obtain the desired behaviour with this strategy. However, Supplier
s allows to turn the above code into lazy, as show by the following code snippet:
<T> T ternary(boolean pred, Supplier<T> first, Supplier<T> second) { if (pred) { return first.get(); } else { return second.get(); } } String val1() { return "first"; } String val2() { return "second"; } String result1 = bool ? val1() : val2(); String result2 = ternary(bool, () -> val1(), () -> val2());
As you can see, optimising the code by exploiting lazy evaluation is quite easy. And accordingly to Mario Fusco, “laziness is probably the only form of performance optimisation which is (almost) never premature”. Indeed, “there is nothing so useless as doing efficiently something that should not be done at all”.
Lazy Evaluation and Java 8 Streams
As mentioned before, Java 8 Streams represent one of the construct that are lazy by definition. Consider the following example:
IntStream.iterate( 1, i -> i+1 ) .map( i -> i * 2 ) .filter( i -> i > 5 ) .findFirst();
Thanks to the lazy nature of streams, the iterate
method allows to create streams that are potentially infinite. Indeed, this method does not create any data structure, but it demands the data generation to the terminal operation (in this case the findFirst
method). Same considerations apply to the map
and filter
methods: intermediate operations are lazy, since they do not perform any action before the terminal operation. These considerations point to a key concept: streams are not data structures, but rather lazy specifications of how to manipulate data.
Putting these ideas into practice should seem conceptually simpler now. However, this is not exactly the case. In fact, Java still has many limitations that, for instance, have been implemented differently in Scala or other languages more oriented towards functional programming. Mario Fusco shared his presentation on SlideShare, where you can find many other interesting examples to understand the practical difficulties in using lazy evaluation in Java.