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.