Question

In Java 8, interfaces can contain implemented methods, static methods, and the so-called "default" methods (which the implementing classes do not need to override).

In my (probably naive) view, there was no need to violate interfaces like this. Interfaces have always been a contract you must fulfill, and this is a very simple and pure concept. Now it is a mix of several things. In my opinion:

  1. static methods do not belong to interfaces. They belong to utility classes.
  2. "default" methods shouldn't have been allowed in interfaces at all. You could always use an abstract class for this purpose.

In short:

Before Java 8:

  • You could use abstract and regular classes to provide static and default methods. The role of interfaces is clear.
  • All the methods in an interface should be overriden by implementing classes.
  • You can't add a new method in an interface without modifying all the implementations, but this is actually a good thing.

After Java 8:

  • There's virtually no difference between an interface and an abstract class (other than multiple inheritance). In fact you can emulate a regular class with an interface.
  • When programming the implementations, programmers may forget to override the default methods.
  • There is a compilation error if a class tries to implement two or more interfaces having a default method with the same signature.
  • By adding a default method to an interface, every implementing class automatically inherits this behavior. Some of these classes might have not been designed with that new functionality in mind, and this can cause problems. For instance, if someone adds a new default method default void foo() to an interface Ix, then the class Cx implementing Ix and having a private foo method with the same signature does not compile.

What are the main reasons for such major changes, and what new benefits (if any) do they add?

Was it helpful?

Solution

A good motivating example for default methods is in the Java standard library, where you now have

list.sort(ordering);

instead of

Collections.sort(list, ordering);

I don't think they could have done that otherwise without more than one identical implementation of List.sort.

OTHER TIPS

The correct answer is in fact found in the Java Documentation, which states:

[d]efault methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces.

This has been a long-standing source of pain in Java, because interfaces tended to be impossible to evolve once they were made public. (The content in the documentation is related to the paper that you linked to in a comment: Interface evolution via virtual extension methods.) Furthermore, rapid adoption of new features (e.g. lambdas and the new stream APIs) can only be done by extending the existing collections interfaces and providing default implementations. Breaking binary compatibility or introducing new APIs would mean that several years would pass before Java 8's most important features would be in common use.

The reason for allowing static methods in interfaces is again revealed by the documentation: [t]his makes it easier for you to organize helper methods in your libraries; you can keep static methods specific to an interface in the same interface rather than in a separate class. In other words, static utility classes like java.util.Collections can now (finally) be considered an anti-pattern, in-general (of course not always). My guess is that adding support for this behavior was trivial once virtual extension methods were implemented, otherwise it probably wouldn't have been done.

On a similar note, an example of how these new features can be of benefit is to consider one class that has recently annoyed me, java.util.UUID. It doesn't really provide support for UUID types 1, 2, or 5, and it cannot be readily modified to do so. It's also stuck with a pre-defined random generator that cannot be overridden. Implementing code for the unsupported UUID types requires either a direct dependency on a third-party API rather than an interface, or else the maintenance of conversion code and the cost of additional garbage collection to go with it. With static methods, UUID could have been defined as an interface instead, allowing real third-party implementations of the missing pieces. (If UUID were originally defined as an interface, we'd probably have some sort of clunky UuidUtil class with static methods, which would be awful too.) Lots of Java's core APIs are degraded by failing to base themselves on interfaces, but as of Java 8 the number of excuses for this bad behavior have thankfully diminished.

It's not correct to say that [t]here's virtually no difference between an interface and an abstract class, because abstract classes can have state (that is, declare fields) while interfaces cannot. It is therefore not equivalent to multiple inheritance or even mixin-style inheritance. Proper mixins (such as Groovy 2.3's traits) have access to state. (Groovy also supports static extension methods.)

It's also not a good idea to follow Doval's example, in my opinion. An interface is supposed to define a contract, but it is not supposed to enforce the contract. (Not in Java anyway.) Proper verification of an implementation is the responsibility of a test suite or other tool. Defining contracts could be done with annotations, and OVal is a good example, but I don't know whether it supports constraints defined on interfaces. Such a system is feasible, even if one does not currently exist. (Strategies include compile-time customization of javac via the annotation processor API and run-time bytecode generation.) Ideally, contracts would be enforced at compile-time, and worst-case using a test suite, but my understanding is that runtime enforcement is frowned upon. Another interesting tool that might assist contract programming in Java is the Checker Framework.

Because you can only inherit one class. If you've got two interfaces whose implementations are complex enough that you need an abstract base class, those two interfaces are mutually exclusive in practice.

The alternative is to convert those abstract base classes into a collection of static methods and turn all the fields into arguments. That would allow any implementor of the interface to call the static methods and get the functionality, but it's an awful lot of boilerplate in a language that's already way too verbose.


As a motivating example of why being able to provide implementations in interfaces can be useful, consider this Stack interface:

public interface Stack<T> {
    boolean isEmpty();

    T pop() throws EmptyException;
 }

There's no way to guarantee that when someone implements the interface, pop will throw an exception if the stack is empty. We could enforce this rule by separating pop into two methods: a public final method that enforces the contract and a protected abstract method that performs the actual popping.

public abstract class Stack<T> {
    public abstract boolean isEmpty();

    protected abstract T pop_implementation();

    public final T pop() throws EmptyException {
        if (isEmpty()) {
            throw new EmptyException();
        else {
            return pop_implementation();
        }
    }
 }

Not only do we ensure all implementations respect the contract, we've also freed them from having to check if the stack is empty and throwing the exception. It's a big win!...except for the fact that we had to change the interface into an abstract class. In a language with single inheritance, that's a big loss of flexibility. It makes your would-be interfaces mutually exclusive. Being able to provide implementations that only rely on the interface methods themselves would solve the problem.

I'm not sure whether Java 8's approach to adding methods to interfaces allows adding final methods or protected abstract methods, but I know the D language allows it and provides native support for Design by Contract. There is no danger in this technique since pop is final, so no implementing class can override it.

As for default implementations of overridable methods, I assume any default implementations added to the Java APIs only rely on the contract of the interface they were added to, so any class that correctly implements the interface will also behave correctly with the default implementations.

Moreover,

There's virtually no difference between an interface and an abstract class (other than multiple inheritance). In fact you can emulate a regular class with an interface.

This is not quite true since you can't declare fields in an interface. Any method you write in an interface can't rely on any implementation details.


As an example in favor of static methods in interfaces, consider utility classes like Collections in the Java API. That class only exists because those static methods can't be declared in their respective interfaces. Collections.unmodifiableList could've just as well been declared in the List interface, and it would've been easier to find.

Perhaps the intent was to provide the ability to create mixin classes by replacing the need for injecting static information or functionality via a dependency.

This idea seems related to how you can use extension methods in C# to add implemented functionality to interfaces.

The two main purposes I see in default methods (some use cases serve both purposes):

  1. Syntax sugar. A utility class could serve that purpose, but instance methods are nicer.
  2. Extension of an existing interface. The implementation is generic but sometimes inefficient.

If it was just about the second purpose, you wouldn't see that in a brand new interface like Predicate. All @FunctionalInterface annotated interfaces are required to have exactly one abstract method so that a lambda can implement it. Added default methods like and, or, negate are just utility, and you aren't supposed to override them. However, sometimes static methods would do better.

As for extension of existing interfaces - even there, some new methods are just syntax sugar. Methods of Collection like stream, forEach, removeIf - basically, it's just utility you don't need to override. And then there are methods like spliterator. The default implementation is suboptimal, but hey, at least the code compiles. Only resort to this if your interface is already published and widely used.


As for the static methods, I guess the others cover it quite well: It allows the interface to be its own utility class. Maybe we could get rid of Collections in Java's future? Set.empty() would rock.

Licensed under: CC-BY-SA with attribution
scroll top