Java 8: too little, too late?

Lately, I attended an event that Oracle organized in Athens, which also served as the inaugural event for Java 8. I consider Java 8 a major release (unlike prior “major” releases) which does merit discussion. And, as I usually play the Devil’s Advocate, I’m going to share some thoughts on it.

I’ll focus on certain new features: the most prominent are Streams and lambdas, but Optional and default methods are also worth discussing. I won’t discuss new time support, or any other library improvement.

Streams is an attempt to give Java a real collection library; only, the library consists of a single interface, the Stream, and you have to convert back and forth between it and the real data structures. Still, it’s exciting to go from almost zero to being able to do map, filter, flatMap, fold, reduce etc in Java.

Lambdas will help a lot to bring some long-needed conciseness to Java. But they’re not really lambda expressions, meaning, anonymous functions. The only place where they can exist is where a Functional interface is expected (and a functional interface, in short, is any interface representing a single concern – more on that later). Combined with some limited type inference, this does give a lot of leverage without doing real changes to the language. If you were planning to do heavy higher-order functional stuff in Java, don’t hold high hopes, because the type signatures you have to write will likely dissuade you. There’s no type inference to ease the burden. And no predefined function interface for three or more arguments. But it’s nice for collection operations and such.

Since you know that I don’t have time for lengthy posts, here is a Java 8 sample which demonstrates, in a single shot, various things about Java 8.

    public static void main(String[] args) {
        final Optional<Integer> integerOptional =
                Arrays.stream(args)
                .findFirst()
                .flatMap(a -> getEl(getTheMap(), a))
                .flatMap(SampleUseOptional::tryParseInt)
                .map(i -> i*2);
    }

    public static <K,V> Optional<V> getEl(Map<K,V> map, K key) {
        return map.containsKey(key) ? Optional.of(map.get(key)) : Optional.empty();
    }

    public static Optional<Integer> tryParseInt(String s) {
        try {
            return Optional.of(Integer.valueOf(s));
        } catch (NumberFormatException e) {
            return Optional.empty();
        }
    }

    private static HashMap<String, String> getTheMap() { /* ... */ }

The meat, of course, is the monadic workflow in main. But notice how artfully I crammed in there

  1. the fact that, even arrays can be made into streams, even though they were second-class collections up to Java 8
  2. the fact that, using defender (default) methods to enhance existing interfaces can do nothing for Java arrays, hence the need for Arrays.stream
  3. the fact that Optional is also a monad in Java 8, as are collections (by virtue of Streams)
  4. the fact that Optional is not integrated where it makes sense, like Map.get or Integer.parse
  5. the fact that there’s shorthand for referring to a method
  6. the fact that there’s no shorthand to refer to a curried method (e.g. getEl(getTheMap())

Let’s work on that lack of currying for a moment. Surely there’s some (verbose) way to define what currying means. Here is an appropriate definition for a two-argument, may I dare say?, function.

    private static <T1,T2,R> Function<T2,R> curryFirstOfTwo(BiFunction<T1,T2,R> f, T1 x) {
        return y -> f.apply(x,y);
    }

Ok, that was not so bad. This is still Java, after all. Let’s try to use it.

        final Optional<Integer> integerOptional3 =
                Arrays.stream(args)
                .findFirst()
                .flatMap(curryFirstOfTwo(SampleUseOptional::getEl, getTheMap()))
                .flatMap(SampleUseOptional::tryParseInt)
                .map(i -> i * 2);

Hmm, IDEA chokes on this, and stops complaining only when the first argument to curryFirstOfTwo is rewritten as a lambda with typed arguments, but the actual compiler fares better. So this amount of boilerplate gives you an idea of what you can expect when you want to go a little further than the design space of the new features. Still, it’s feasible. And don’t forget all the fun, implementing interfaces for functions of arity N > 2!

I didn’t cover what Optional is, because it is what its name implies: it’s what Scala and F# call Option, Haskell calls Maybe and Guava already had with the same name. It’s your way out of NPE Hell, and how you’ll avoid being ridiculed by your C/C++ programmer friends to which you dared boast one day that programming in Java is safer. They’re still laughing, but not for long. The simplicity of this idea makes it be misunderstood. How can anything so simple have so much value? Aren’t nullity declarations, non-nullable types, nullity inference, elvis operators and such (found in Kotlin, Gosu, Fantom), similar, if not better? No, in my opinion, they aren’t. But this is a discussion for another day. The important thing is that Optional is now part of Java 8, and yet it isn’t, since the whole behemoth of its library is still exception-waiting-to-happen-ridden. But it can be part of your codebase.

Default methods, which were added to allow Oracle to extend interfaces, but now allow anyone to do as much, bring multiple (implementation) inheritance in Java. It’s that simple. No diamond problem, according to a presentation from Oracle I watched. At least, no diamond problem if you restrict the problem to how state is initialized in multiple inheritance, not how the rest of the world defines it, in a more general sense. With great power comes great responsibility, and Java, breaking out of its cocoon, starts having grown-up-language problems. Welcome, Java!

So, is Java 8 too little, too late? In my opinion, no and no. It doesn’t come up ahead. It just plays catch with everyone else. But it’s the first time it did anything to approach everyone else in a long time.

Already, Java 8 supersedes Guava for functional-ish programming (my up-to-now escape to sanity while doing Java), which had opted for a more limited set of features. Will it clash with Scala? Certainly not. If anything, it will pull more developers out of the stagnating waters older versions of Java had left them in. Thousands of people had stopped thinking about where programming can go to. Out of some twisted notion of devotion to Java? Out of safety in doing what thousands of others do? Out of arrogance for belonging to the biggest programming community? Something, in any case. And now, it is these people that are the least qualified to make the move to Java 8, while anyone with experience in any other JVM, .Net or any other language should feel right at home.

However, I am pleased to say that many more thousands of people are thirsty for more, which is a sign of sanity in the community. Adoption of Java 8 has a chance of being a lot faster than that of previous releases, and any JVM language will benefit from that happening. Not to mention that, increasing the momentum of the JVM might trigger faster evolution of over-the-horizon features like value classes and real arrays.

Advertisements