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.

Generics Face-Off, Nugget #1

My last post was a bit overly enthusiastic about Scala. To counterbalance it, I present another “nugget” in this post, inspired by my slim experience on using Scala together with Java. You see, Scala aims to remedy some shortcomings of the Java typesystem, in particular its generics, which makes it somewhat uncomfortable, at times, to bridge the two. And the face-off you read in the title, will be between Scala and another JVM language, Kotlin, which aims more towards being a better Java than providing a wholly different solution. Let’s see how they do, using the scale used by Catch-22‘s Colonel Cathcart.

Java’s wacky use of Generics

Suppose there’s a generic Java class, named Something, presented here as a bare-bones example.

public class Something<T> {
    @Override
    public String toString() {
        return "Something";
    }
}

And here’s the rub. Java allows you to “cop out” of Generics and use either the erased type, or an existential type, in the following meaningless way (and I’m not making this up).

public class JTest {
    public static Something<?> getSomething() {
        return new Something<String>();
    }
}

Here’s some Scala code to process an instance of Something<String>.

class STest {
    def workWithSomethingString(s : Something[String]) {
        print(s)
    }
}

Suppose you know that the particular instance is a Something<String> (the way you know it lies certainly outside of the type system). Going from Java to Scala produces an unchecked warning, as it should.

    public static void scalaDriver() {
        STest sTest = new STest();
        sTest.workWithSomethingString((Something<String>)getSomething()); 
        //flagged as unckecked
    }

Enter Kotlin.

class KTest {
    fun workWithSomethingString(s:Something<String>) {
        print(s)
    }
}

Same thing here.

    public static void kotlinDriver() {
        KTest ktest = new KTest();
        ktest.workWithSomethingString((Something<String>) getSomething());
        //flagged as unckecked
    }

Both fail to cope with Java’s incongruent use of Generics. Hence, a black eye for both, in that regard. Hint: at the call site, it’s the Java compiler that runs the show. There’s really no way to win. Let’s see what we get if we give full control of the calling site to the other compilers.

Reified generics

If we disregard direct interconnection with Java, for a moment, both Scala and Kotlin want to provide some solution to being able to recover type information that is lost by erasure.

Scala does it using TypeTags and such, in various ways which employ the compiler to supply information about generic types from the calling site to the called method.

Below is one way to do it. The first call to workWithSomething delegates correctly to workWithSomethingString.

    def passTheBuck() {
        workWithSomething(new Something[String])
        workWithSomething(new Something[Int])
    }

    def workWithSomething[T](s : Something[T])(implicit t : TypeTag[T]) {
        if( t.tpe.typeConstructor =:= typeOf[String] ) {
            workWithSomethingString(s.asInstanceOf[Something[String]])
        } else {
            print("\nCannot handle\n")
        }
    }

How about Kotlin? Kotlin has promised reified generics, but it’s still in the roadmap. Black eye for Kotlin, feather in Scala’s cap.

Things don’t look so good for Kotlin, so far. In the next nugget, let’s see if it manages to recoup, using its distinguishing feature of mixed-site variance.