Java 8 Friday: The Dark Side of Java 8


At Data Geekery, we love Java. And as we’re really into jOOQ’s fluent API and query DSL, we’re absolutely thrilled about what Java 8 will bring to our ecosystem.

Java 8 Friday

Every Friday, we’re showing you a couple of nice new tutorial-style Java 8 features, which take advantage of lambda expressions, extension methods, and other great stuff. You’ll find the source code on GitHub.

The dark side of Java 8

So far, we’ve been showing the thrilling parts of this new major release. But there are also caveats. Lots of them. Things that

  • … are confusing
  • … are wrong
  • … are omitted (for now)
  • … are omitted (for long)

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?

Overloading gets even worse

Overloading, generics, and varargs aren’t friends. We’ve explained this in a previous article, and also in this Stack Overflow question. These might not be every day problems in your odd application, but they’re very important problems for API designers and maintainers.

With lambda expressions, things get “worse”. So you think you can provide some convenience API, overloading your existing run() method that accepts a Callable to also accept the new Supplier type:

static <T> T run(Callable<T> c) throws Exception {
    return c.call();
}

static <T> T run(Supplier<T> s) throws Exception {
    return s.get();
}

What looks like perfectly useful Java 7 code is a major pain in Java 8, now. Because you cannot just simply call these methods with a lambda argument:

public static void main(String[] args)
throws Exception {
    run(() -> null);
    //  ^^^^^^^^^^ ambiguous method call
}

Tough luck. You’ll have to resort to either of these “classic” solutions:

    run((Callable<Object>) (() -> null));
    run(new Callable<Object>() {
        @Override
        public Object call() throws Exception {
            return null;
        }
    });

So, while there’s always a workaround, these workarounds always “suck”. That’s quite a bummer, even if things don’t break from a backwards-compatibility perspective.

Not all keywords are supported on default methods

Default methods are a nice addition. Some may claim that Java finally has traits. Others clearly dissociate themselves from the term, e.g. Brian Goetz:

The key goal of adding default methods to Java was “interface evolution”, not “poor man’s traits.”

As found on the lambda-dev mailing list.

Fact is, default methods are quite a bit of an orthogonal and irregular feature to anything else in Java. Here are a couple of critiques:

They cannot be made final

Given that default methods can also be used as convenience methods in API:

public interface NoTrait {

    // Run the Runnable exactly once
    default final void run(Runnable r) {
        //  ^^^^^ modifier final not allowed
        run(r, 1);
    }

    // Run the Runnable "times" times
    default void run(Runnable r, int times) {
        for (int i = 0; i < times; i++)
            r.run();
    }
}

Unfortunately, the above is not possible, and so the first overloaded convenience method could be overridden in subtypes, even if that makes no sense to the API designer.

They cannot be made synchronized

Bummer! Would that have been difficult to implement in the language?

public interface NoTrait {
    default synchronized void noSynchronized() {
        //  ^^^^^^^^^^^^ modifier synchronized
        //  not allowed
        System.out.println("noSynchronized");
    }
}

Yes, synchronized is used rarely, just like final. But when you have that use-case, why not just allow it? What makes interface method bodies so special?

The default keyword

This is maybe the weirdest and most irregular of all features. The default keyword itself. Let’s compare interfaces and abstract classes:


// Interfaces are always abstract
public /* abstract */ interface NoTrait {

    // Abstract methods have no bodies
    // The abstract keyword is optional
    /* abstract */ void run1();

    // Concrete methods have bodies
    // The default keyword is mandatory
    default void run2() {}
}

// Classes can optionally be abstract
public abstract class NoInterface {

    // Abstract methods have no bodies
    // The abstract keyword is mandatory
    abstract void run1();

    // Concrete methods have bodies
    // The default keyword mustn't be used
    void run2() {}
}

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:

public interface NoTrait {
    void run1();
    void run2() {}
}

public abstract class NoInterface {
    void run1();
    void run2() {}
}

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.

While visibility modifiers were discussed on the lambda-dev mailing list, but were out of scope for this release. Maybe, we can get them in a future release.

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 Set would 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.function package, 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 a Predicate, 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, a UnaryOperator, 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 return void? It’s called a Consumer
  • Does your function return boolean? It’s called a Predicate
  • Does your function return an int, long, double? It’s called XXToIntYY, XXToLongYY, XXToDoubleYY something
  • Does your function take no arguments? It’s called a Supplier
  • 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 called BinaryOperator
  • 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!

More on Java 8

Java 5 generics have brought a lot of great new features to the Java language. But there were also quite a few caveats related to type erasure. Java 8’s default methods, Streams API and lambda expressions will again bring a lot of great new features to the Java language and platform. But we’re sure that Stack Overflow will soon burst with questions by confused programmers that are getting lost in the Java 8 jungle.

Learning all the new features won’t be easy, but the new features (and caveats) are here to stay. If you’re a Java developer, you better start practicing now, when you get the chance. Because we have a long way to go.

Nonetheless, thigns are exciting, so stay tuned for more exciting Java 8 stuff published in this blog series.

Are you in for another critique about Java 8? Read “New Parallelism APIs in Java 8: Behind the Glitz and Glamour” by the guys over

Tags: , , , , , , , , , , , ,

17 responses to “Java 8 Friday: The Dark Side of Java 8”

  1. JB Nizet says :

    If default methods were allowed to be final, you could not implement 2 different interfaces containing the same method anymore as soon as one is final and you want to choose the other implementation. `synchronized` has to do with concurrent access to shared state, and interfaces don’t have any state, so I find it quite normal to let that as a choice to the implementation. And since default methods can’t be final, you can always override the default methods just to make them synchronized.

    • Maaartinus says :

      Good point concerning final, but this problem already exists as you can’t implement both Collection and something declaring long size().

      I disagree with synchronized as you can synchronize on class X {} which has no state either. You can also enclose the whole method body in synchronized (this) which is AFAIK equivalent. I’d guess this should work for interfaces as well.

      • lukaseder says :

        You can also enclose the whole method body in synchronized (this) which is AFAIK equivalent.

        Yes, except that putting synchronized into the signature formally makes it part of the method contract.

    • lukaseder says :

      If default methods were allowed to be final, you could not implement 2 different interfaces containing the same method anymore as soon as one is final and you want to choose the other implementation.

      I can see hwo this might have been a compelling enough argument for the expert group to leave final out of the game for interfaces. Yet, you may run into similar issues if 2 different interfaces contain the “same” method returning incompatible types, already since Java 1.0:

      interface I1 {
          int get();
      }
      
      interface I2 {
          long get();
      }
      

      You cannot implement both of the above interfaces. So in my opinion, the added value for API designers of having default final methods outweights the rare edge-cases, where people run into this kind of issue.

      Synchronized: It might be used as a contract for implementations, similar to Hashtable which made that a contract for subtypes like Properties. Agreed, this isn’t very contemporary and would probably be useful only in very remote edge-cases.

      • Maaartinus says :

        Actually, synchronized in the method signature is rather misleading as it doesn’t get inherited. You could consider it to be a combination of an implementation detail with a documentation, but a collection interface method documenting all implementations to be synchronized is pretty much useless (neither ConcurrentHashMap nor ImmutableMap would comply; actually no usable class would). You may require all implementations to be thread-safe in a sense, but there are too many useful options (and synchronized doesn’t belong to them).

        • lukaseder says :

          Actually, synchronized in the method signature is rather misleading as it doesn’t get inherited.

          Good point. I hadn’t thought of that.

  2. mwanji says :

    Why would you overload Callable with Supplier, since both have the same lamba signature?

    Also, I believe equals and hashcode are never added as default methods on interfaces because default methods always defer to methods on classes. As both are implemented on Object, the default would never be used.

    Not too sure about listIterator, but it would have required moving the method from AbstractList to List, which might have caused binary backwards incompatibility?

    • lukaseder says :

      Why would you overload Callable with Supplier, since both have the same lamba signature?

      Just because we now have lambdas doesn’t mean that lambdas is all we ever do. The two methods are very different methods. If you’ve written a ton of JDK 6 concurrent API with lots of Callables, and now you write a ton of JDK 8 streams API with lots of Suppliers, you *might* just think that you should overload this method for convenience. Of course, you shouldn’t, but this is not very obvious at first.

      As both are implemented on Object, the default would never be used.

      You’re right, and in fact, the compiler prevents this. You’re not allowed to have such default methods in interfaces. While this makes sense from a language perspective, it’s kind of weird because all sorts of contracts for equals() and hashCode() are already overwritten. Why not be able to provide a default implementation for such a contract? I guess this shows that the whole notion of methods from java.lang.Object is a bit quirky (e.g. the wait() methods)

      Not too sure about listIterator, but it would have required moving the method from AbstractList to List, which might have caused binary backwards incompatibility?

      Interesting thought. Could you show an example?

      • Ian Robertson says :

        Another problem with adding default implementations of methods which already existed on the interface previously is that it could break backwards compatibility. For example, there might have been code that depended on MyList’s equals method being the default implementation from Object. While such code is a clear violation of spec, Oracle goes out of their way to avoid breaking code that already “works”.

        As for listIterator – that’s a harder one to figure out – hard to imagine code depending on a NoSuchMethodError. I don’t think binary compatibilty would be an issue, since it appears that default methods are invoked with invokeinterface when called against the interface, and invokevirtual when called against the class, just like other methods.

        • lukaseder says :

          For example, there might have been code that depended on MyList’s equals method being the default implementation from Object

          While that is a remote possibility, I doubt that this is a motivation for the expert group to have decided upon things the way they are. Other comments on this post have indicated that no matter what default method matches Object.equals(), the actual Object.equals() implementation will always take precedence. It is simply not possible to override a method from the concrete class hierarchy from an interface. Hence, it simply doesn’t make sense to implement a default List.equals() method.

          As for listIterator – that’s a harder one to figure out – hard to imagine code depending on a NoSuchMethodError

          Yes, that’s a better example than equals()

  3. Maaartinus says :

    Oracle not adding the default methods to List should be no problem as you could do it in YourList extends List. Unfortunately, many of them use modCount, which you can’t have in an interface.

    You could provide it in the implementations (it’s just one field) and use abstract accessors in YourList, but they would have to be public. Too bad they din’t fix the visibility problem.

    • lukaseder says :

      Oracle not adding the default methods to List should be no problem as you could do it in YourList extends List.

      Yes, you could, of course.

      Unfortunately, many of them use modCount, which you can’t have in an interface.

      Which leads to the other feature omission that you’ve mentioned, too: visibility modifiers on default methods (or on any interface methods). That, too was discussed on the lambda list, and omitted because of the time constraints, and some uncertainty about how to handle package visibility on interface methods (e.g. by introducing a package keyword).

      • Maaartinus says :

        Agreed. Interfaces forcing all methods to be public was a terrible idea and it’s pity it wasn’t fixed now. Given that package is already a keyword and that it can’t appear anywhere but in the package declaration, it’d be no problem to recycle it for access control, although recycling default feels better (which is not an option anymore; I agree with your arguments).

  4. Edwin Dalorzo says :

    What could be the purpose of a synchronized method on a interface? After all the interface does not have state, so what would you be synchronizing access to?

    Also, final interface methods? Does that make sense? After all the whole idea of an interface is to create implementations of it. You are saying that you want to seal an interface method because it has a default implementation?

    • lukaseder says :

      What could be the purpose of a synchronized method on a interface?

      One reason is to establish a contract already on the interface level (why not…). Another is: Because you can. I mean, you can write this:

      interface I {
          default void sneakySynchronized() {
              // Unexpected!
              synchronized(this) {
              }
          }
      }
      

      And because this can be done, why not just allow promotion to the API even if it’s useful only in remote edge-cases?

      that you want to seal an interface method because it has a default implementation?

      Yes, because that’s the whole point of final. Imagine overloaded convenience methods:

      interface I {
      
          // No point in overriding this
          final default void run() {
              run(null, null);
          }
      
          // No point in overriding this
          final default void run(SomeType x) {
              run(x, null);
          }
      
          void run(SomeType x, SomeOtherType y);
      }
      
      • Edwin Dalorzo says :

        Let me play the Devil’s advocate here, just for the purpose of making the conversation interesting.

        In the case of the synchronized suggestion, this could be something that could have been argued of the traditional interfaces prior to the JDK 8 as well, but we know that keywords like synchronized, final and strictfp should go in concrete implementations and not interfaces because otherwise it would force every implementor to pay a price they may not be interested in. That question would be what if I want an implementation of such interface not to be synchronized. Also, is hard to reason about synchronization in an interface, I mean, you don’t even know what the state of the implementor would be, how do you you synchronizing this would prevent a race condition.

        They actually David Holmes and Joseph Darcy discussed some of this in the Lambda Mailing List (http://mail.openjdk.java.net/pipermail/lambda-dev/2012-October/006084.html)

        On the other hand, final methods on an interface are kind of counterintuitive for me since the hole purpose of the interface is to be implemented. I think such weird cases should probably fit better in an abstract class, but if that were the case, then we should not only support final, should support other modifiers like protected and private as well. Ultimately, an interface would be just like a class, but without state. I guess with interface now supporting implementations, it becomes a gray area how far interfaces should go. Time will tell, I guess, and this is what the expert group is expecting. You can see Brian Goetz said:

        “[...] we expect that this will inevitably create pressure for
        additional features in interfaces, like, say, private methods. Some of
        these we may try and anticipate by including in this round; others we
        may wait for usage patterns to emerge [...]”

        http://mail.openjdk.java.net/pipermail/lambda-dev/2011-December/004361.html

        • lukaseder says :

          Thanks for digging up those links. I wasn’t aware of the first one where synchronized and strictfp were discussed. One can also see the importance that was given to this discussion :-) Myself, I wouldn’t want to argue about synchronized (or strictfp, which I had forgotten in the blog post) too long. In the end of the day, the added value for any API designer is marginal, as opposed to final and visibility modifiers, which would be an essential further evolution of interfaces being classes without state – which they should be, in my opinion, and which is again an argument against the default keyword.

          Another argument in favour of final in interfaces is the possibility to prevent shadowing of static methods that may now be declared in interfaces. This type of shadowing is hardly every useful, and in my opinion, a complete anti-pattern – i.e. by default, all static methods should be written as static final, also in interfaces. But that obviously won’t happen in the JDK

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 1,745 other followers

%d bloggers like this: