The dark side of Java 8

We’ve mostly focused on the sweet new additions to Java 8 – but there are two sides to every release. Lukas Eder delves into the murkier side of the update.

This post was originally published over at jooq.org as part of a special series
focusing on all things Java 8, including how take advantage of
lambda expressions, extension methods, and other great
stuff. You’ll find the
source code on GitHub.

There are always two sides to Java major releases. On the bright
side, we get lots of new functionality that most people would say
was overdue. Other languages, platforms have had
generics long before Java 5. Other languages, platforms have had
lambdas long before Java 8. But now, we finally have these
features. In the usual quirky Java-way.

Lambda expressions were introduced quite elegantly. The idea of
being able to write every anonymous SAM instance as a lambda
expression is very compelling from a backwards-compatiblity point
of view. So what are the dark sides to Java
8?

If the language were re-designed from scratch, it would probably
do without any
of abstract or default keywords.
Both are unnecessary. The mere fact that there is or is not a body
is sufficient information for the compiler to assess whether a
method is abstract. I.e, how things should be:

The above would be much leaner and more regular. It’s a pity
that the usefulness of default was never
really debated by the EG. Well, it was debated but the EG never
wanted to accept this as an option. I’ve
tried my luck, with this response:

I don’t think #3 is an option because interfaces with method
bodies are unnatural to begin with. At least specifying the
“default” keyword gives the reader some context why the language
allows a method body. Personally, I wish interfaces would remain as
pure contracts (without implementation), but I don’t know of a
better option to evolve interfaces.

Again, this is a clear commitment by the EG not to commit to the
vision of “traits” in Java. Default methods were a pure necessary
means to implement 1-2 other features. They weren’t well-designed
from the beginning.

Other modifiers

Luckily, the static modifier made it into
the specs, late in the project. It is thus possible to specifiy
static methods in interfaces now. For some reason, though, these
methods do not need (nor allow!)
the default keyword, which must’ve been a
totally random decision by the EG, just like you apparently cannot
define static final methods in
interfaces.

Few default methods were actually implemented

Some methods would have sensible default implementations on
interface – one might guess. Intuitively, the collections
interfaces, like List or Setwould
have them on their equals() and hashCode() methods,
because the contract for these methods is well-defined on the
interfaces. It is also implemented in AbstractList,
using listIterator(),
which is a reasonable default implementation for most tailor-made
lists.

It would’ve been great if these API were retrofitted to make
implementing custom collections easier with Java 8. I could make
all my business objects implement List for
instance, without wasting the single base-class inheritance
on AbstractList.

Probably, though, there has been a compelling reason related to
backwards-compatibility that prevented the Java 8 team at Oracle
from implementing these default methods. Whoever sends us the
reason why this was omitted will get a free jOOQ
sticker:-)

The wasn’t invented here – mentality

This, too, was criticised a couple of times on the lambda-dev EG
mailing list. And while writing this blog series, I can only
confirm that the new functional interfaces are very confusing to
remember. They’re confusing for these reasons:

Some primitive types are more equal than
others

The int, long, double primitive
types are preferred compared to all the others, in that they have a
functional interface in the java.util.functionpackage,
and in the whole Streams API. boolean is a
second-class citizen, as it still made it into the package in the
form of a BooleanSupplier or
aPredicate,
or worse: IntPredicate.

All the other primitive types don’t really exist in this area.
I.e. there are no special types
for byte, short, float,
and char. While the argument of meeting deadlines
is certainly a valid one, this quirky status-quo will make the
language even harder to learn for newbies.

The types aren’t just called Function

Let’s be frank. All of these types are simply “functions”. No
one really cares about the implicit difference between
a Consumer,
a Predicate,
aUnaryOperator,
etc.

In fact, when you’re looking for a type with a
non-void return value and two arguments, what
would you probably be calling it? Function2?
Well, you were wrong. It is called a BiFunction.

Here’s a decision tree to know how the type you’re looking for
is called:

Does your function take a
single int, long, double argument?
It’s called
an IntXX, LongXX, DoubleXX something

Does your function take two arguments? It’s
called BiXX

Does your function take two arguments of the same type? It’s
calledBinaryOperator

Does your function return the same type as it takes as a single
argument? It’s called UnaryOperator

Does your function take two arguments of which the first is a
reference type and the second is a primitive type? It’s
called ObjXXConsumer(only consumers exist with
that configuration)

Else: It’s called Function

Good lord! We should certainly go over to Oracle Education to
check if the price for Oracle Certified Java
Programmer courses have drastically increased, recently…
Thankfully, with Lambda expressions, we hardly ever have to
remember all these types!