I know, all the Scala fanboys are going to hate me now. But: Stop overusing lambda expressions.

Most of the time when you are using lambdas, you are not even doing functional programming, because you often are violating one key rule of functional programming: no side effects.

For example:

collection.forEach(System.out::println);

is of course very cute to use, and is (wow) 10 characters shorter than:

for (Object o : collection) System.out.println(o);

but this is not functional programming because it has side effects.

What you are doing are anonymous methods/objects, using a shorthand notion. It’s sometimes convenient, it is usually short, and unfortunately often unreadable, once you start cramming complex problems into this framework.

It does not offer efficiency improvements, unless you have the propery of side-effect freeness (and a language compiler that can exploit this, or parallelism that can then call the function concurrently in arbitrary order and still yield the same result).

Here is an examples of how to not use lambdas:
DZone Java 8 Factorial (with boilerplate such as the Pair class omitted):

Stream<Pair> allFactorials = Stream.iterate(
  new Pair(BigInteger.ONE, BigInteger.ONE),
  x -> new Pair(
    x.num.add(BigInteger.ONE),
    x.value.multiply(x.num.add(BigInteger.ONE))));
return allFactorials.filter(
  (x) -> x.num.equals(num)).findAny().get().value;

When you are fresh out of the functional programming class, this may seem like a good idea to you… (and in contrast to the examples mentioned above, this is really a functional program).
But such code is a pain to read, and will not scale well either. Rewriting this to classic Java yields:

BigInteger cur = BigInteger.ONE, acc = BigInteger.ONE;
while(cur.compareTo(num) <= 0) {
  cur = cur.add(BigInteger.ONE); // Unfortunately, BigInteger is immutable!
  acc = acc.multiply(cur);
}
return acc;

Sorry, but the traditional loop is much more readable. It will still not perform very well (because of BigInteger not being designed for efficiency

  • it does not even make sense to allow BigInteger for num - the factorial of 2**63-1, the maximum of a Java long, needs 1020 bytes to store, i.e. about 500 exabyte.

For some, I did some benchmarking. One hundred random values num (of course the same for all methods) from the range 1 to 1000.

I also included this even more traditional version:

BigInteger acc = BigInteger.ONE;
for(long i = 2; i <=x; i++) {
  acc = acc.multiply(BigInteger.valueOf(i));
}
return acc;

Here are the results (Microbenchmark, using JMH, 10 warum iterations, 20 measurement iterations of 1 second each):

functional    1000     100  avgt   20  9748276,035 ± 222981,283  ns/op
biginteger    1000     100  avgt   20  7920254,491 ± 247454,534  ns/op
traditional   1000     100  avgt   20  6360620,309 ± 135236,735  ns/op

As you can see, this “functional” approach above is about 50% slower than the classic for-loop. This will be mostly due to the Pair and additional BigInteger objects created and garbage collected.

Apart from being substantially faster, the iterative approach is also much simpler to follow. (To some extend it is faster because it is also easier for the compiler!)

There was a recent blog post by Robert Bräutigam that discussed exception throwing in Java lambdas and the pitfalls associated with this. The discussed approach involves abusing generics for throwing unknown checked exceptions in the lambdas, ouch.


Don’t get me wrong. There are cases where the use of lambdas is perfectly reasonable. There are also cases where it adheres to the “functional programming” principle. For example, a stream.filter(x -> x.name.equals("John Doe")) can be a readable shorthand when selecting or preprocessing data. If it is really functional (side-effect free), then it can safely be run in parallel and give you some speedup.

Also, Java lambdas were carefully designed, and the hotspot VM tries hard to optimize them. That is why Java lambdas are not closures - that would be much less performant. Also, the stack traces of Java lambdas remain somewhat readable (although still much worse than those of traditional code). This blog post by Takipi showcases how bad the stacktraces become (in the Java example, the stream function is more to blame than the actual lambda - nevertheless, the actual lambda application shows up as the cryptic LmbdaMain$$Lambda$1/821270929.apply(Unknown Source) without line number information). Java 8 added new bytecodes to be able to optimize Lambdas better - earlier JVM-based languages may not yet make good use of this.

But you really should use lambdas only for one-liners. If it is a more complex method, you should give it a name to encourage reuse and improve debugging.

Beware of the cost of .boxed() streams!

And do not overuse lambdas. Most often, non-Lambda code is just as compact, and much more readable. Similar to foreach-loops, you do lose some flexibility compared to the “raw” APIs such as Iterators:

for(Iterator<Something>> it = collection.iterator(); it.hasNext(); ) {
  Something s = it.next();
  if (someTest(s)) continue; // Skip
  if (otherTest(s)) it.remove(); // Remove
  if (thirdTest(s)) process(s); // Call-out to a complex function
  if (fourthTest(s)) break; // Stop early
}

In many cases, this code is preferrable to the lambda hacks we see pop up everywhere these days. Above code is efficient, and readable.
If you can solve it with a for loop, use a for loop!

Code quality is not measured by how much functionality you can do without typing a semicolon or a newline!

On the contrary: the key ingredient to writing high-performance code is the memory layout (usually) - something you need to do low-level.

Instead of going crazy about Lambdas, I’m more looking forward to real value types (similar to a struct in C, reference-free objects) maybe in Java 9 (Project Valhalla), as they will allow reducing the memory impact for many scenarios considerably. I’d prefer a mutable design, however - I understand why this is proposed, but the uses cases I have in mind become much less elegant when having to overwrite instead of modify all the time.