Sunday, October 24, 2010

6 10 Things I Hate About Java (or, Scala is the Way and the Light)

I've been working with Java quite extensively for about 4 years now, and it has been enjoyable for the most part. Garbage collection, the JVM, generics, anonymous classes, and superb IDE support have made my life much easier.

But a few things make me gnash my teeth on a daily basis, and it's funny, but none of them are issues in another JVM language in which I have been dabbling, Scala. It seems the developers of that language felt my pain as well.

  1. Miserable type inference. Apparently some of the problems with it are being addressed in project coin for Java 7. The blue portions of the following code are, to any sane programmer, maddeningly superfluous, but nevertheless strictly required in Java until at least mid-2011:

    List< Integer > li1 = new ArrayList< Integer >();
    List< Integer > li2 = Arrays.asList( 1, 2, 3 );
    o.processListOfInteger( Arrays.< Integer >asList() );

    Needless to say, equivalent initializations in Scala require no such redundant information.
  2. Generic invariance. An example from last week: I'm working on an implementation FrazzleExecutorService of java.util.concurrent.ScheduledExecutorService and a refinement FrazzleFuture< T > of java.util.ScheduledFuture< T >. Covariant subtyping lets me get away with returning a FrazzleFuture< T > from a method like FrazzleExecutorService.submit() without violating the contract. But I can't return List< FrazzleFuture< T > > from FrazzleExecutorService.invokeAll() because (a) ScheduledExecutorService would have had to declare the return type to be List< ? extends ScheduledFuture< T > >; (b) the returned list should have been immutable anyway; (c) generic types like List< T > are invariant in their parameters. In Scala, there is a separate mutable and immutable collections hierarchy, and at least in the immutable one, S <: T implies List[ S ] <: List[ T ], because List is declared covariant in its parameter.
  3. Collections can't be tersely initialized. Part of the blame is the Collections framework; part of it is the goddamn language. The following code illustrates, with green text indicating typical verbosity:

    List< Integer > li1 = new ArrayList< Integer >(
        Arrays.asList(
    1, 2, 3 )
    )
    ;
    @SuppressWarnings( "serial" )
    Map< String, String > mss1 = new HashMap< String, String >() { {
        put
    ( "foo", "bar" );
        put
    ( "this", "sucks" );
    } }
    ;

    Turns out the collections literals are also not supported in Scala; you have to type List( 1, 2, 3 ) or Map( "foo" -> "bar", "this" -> "rocks" ). Excuse me if I mock Java incessantly at this point. Collection improvements have been postponed until Java 8, scheduled for mid-2012.
  4. Higher-order programming is absurdly verbose. Rather than give code samples, I'll just refer you to these guys and let you see for yourself how even a library can't save you from massive boilerplate for the simplest things. And Scala? Lambda expressions are built-in as syntactic sugar for functional objects, making higher-order code simple, readable, and terse.
  5. Modeling variant types is awkward. You have to choose from among many bad options:
    RepresentationInterrogation
    one sparsely-populated class (S + T modeled as S × T)if-ladders based on comparing to null (see 8)
    S × T and an enum of type labelsswitch + casting
    a hierarchya bunch of isS() and isT() methods and casting
    a hierarchyvarious casting attempts wrapped with ClassCastException catch blocks (ok, that's not really an option, but I get that as an answer in interviews sometimes)
    a hierarchyif-ladders based on instanceof and casting
    a hierarchypolymorphic decomposition and the inevitable bloated APIs at the base class that result
    a hierarchy that includes Visitorpainfully verbose visitors (see 4 and 10)
    a hierarchy of Throwablesthrow and various catch blocks, which I suspect compiles to the same thing as the instanceof approach, only more expensive (but actually requires the least code!)
    Scala has case classes and pattern matching built in.
  6. No tuples. One ends up either creating Pair< S, T > or dozens of throwaway classes with "And" in the name, like CountAndElapsed. Scala has tuples, although I feel like they kind of screwed up by not going the full ML and making multi-argument functions/methods be the same as single-argument functions/methods over tuples. So to call a 2-arg function f with a pair p = ( p1, p2 ), you can either call f( p1, p2 ) or f.tupled( p ). There must be some deep reason for making the distinction.
  7. No mixins. If you need stuff from 2 abstract classes, you will be copying, or aggregating (with loads of monkey delegation boilerplate) at least one of the two.
  8. Null. The following code should illustrate:

    private static Doohickey getDoohickey( Thingamajigger t ) {
        Whatsit w;
        Foobar f;
        if ( null == t )
            return null;
        else if ( null == ( w = t.getWhatsit() ) )
            return null;
        else if ( null == ( f = w.getFoobar() ) )
            return null;
        else
            return f.getDoohickey();
    }

    I believe the "Elvis" operator was developed to solve this annoyance (return t.?getWhatsit().?getFoobar().?getDoohickey();) but it did not make the cut for Java 7 or even Java 8, from what I understand. Scala's solution to this issue is to recommend that operations which might not have a value for you return Option[ T ] instead of T. You can then map your method call to the Option and get back another Option without ever seeing a null pointer exception. Option is a variant type, easily modeled in Scala but not in Java (see 5).
  9. Iterators. They can't throw checked exceptions. They have to implement remove(), often by throwing (unchecked) UnsupportedOperationExceptions. For-each syntax can't work with iterators directly. None of these problems arise with the superb collections framework in Scala which is designed hand-in-hand with its clean higher-order programming (see 4).
  10. Void. This is a holdover from C, and is obviously not anything Java can get rid of, but it's stupid. Because of void, e.g., I can never do a visitor pattern with just one kind of visitor; there has to be one whose methods return a generic type T, and another whose methods return void. And don't try to sell me on the psuedo-type Void, because you still have to accept or return null somewhere. Scala has a type Unit with a single trivial value (), and unitary methods/functions can explicitly return that value or not return anything; the semantics are the same. Thus all expressions have some meaningful type, and classes with generic types can be fully general.