Thursday, April 2, 2015

Default methods in Java

Default methods were added in Java 8. They are also known as defender methods or virtual extension methods. The prime purpose for addition of these methods is to allow us to add new functionality to existing interfaces in old libraries and to make sure the code is also binary compatible with those interfaces.

When an interface contains a default method and another interface extends this interface we can do one of the following:
  • We do not mention the default method and in that case that default method is inherited by new interface.
  • We can re-declare the method and now this is abstract.
  • We can redefine the method and now it is overridden.
One aspect where confusion generally arises is the diamond pattern where a class implements two interfaces and both the interfaces define a method with same signature. Consider the following example:
public interface Engine {  
    default void start(){  
        System.out.println("Starting engine Engine.start()");  
    }  
}

public interface CNGKit {
    default void start(){
        System.out.println("Starting CNG Kit CNGKit.start()");
    }
}

public class Car implements Engine, CNGKit {

}

A car can have both an engine and a CNG Kit and in that case we need to specify whether the car should run on engine (petrol or diesel) or it should run using CNG Kit. This example is just used to explain the concept and not an ideal representation of OOP concepts. This will not compile and we need to resolve it manually by overriding the conflicting method as:
public class Car implements Engine, CNGKit {
 public void start(){
        Engine.super.start();
    }
}

What is the motivation of adding default methods in Java?
  1. They provide option to extend existing libraries by providing new methods and also ensure binary compatibility. An example is the addition of forEach method to Collection where the default implementation is written in terms of iterator method.
  2. They provide flexibility to allow an interface to define method implementations that will work as default in case a concrete class (implementation of this interface) fails to provide an implementation for this default method.
  3. Due to default methods, an interface can stay as a functional interface even if it has more than one method. A Functional Interface has only one non-default abstract method which can be implemented using a lambda expression.
Regarding the last point consider an example of functional interface Predicate which has only one non-default abstract method test. But it provides default methods for negating a predicate or combining it with another predicate. Without default methods these methods had to be provided in another utility class like the pre-Java 8 Collections class (as we don’t want to give up the possibility of lambda implementations for such an interface).

Why default methods cannot be declared final?
Consider the following abstract class Dispatcher which can be used to dispatch an event with empty message or an event with specific message:
abstract class Dispatcher {
    final void dispatchMessage() {
        dispatchMessage(null);
    }
    abstract void dispatchMessage(String message);
}
The method declared final is a convenience method to dispatch an empty message where as other method should be implemented by implementation classes. We can think of using an interface like following, if default methods were allowed to be final:
public interface IDispatcher {
    default final void dispatchMessage() {
        dispatchMessage(null);
    }
    void dispatchMessage(String message);
}

Now it seems like a good use case then how come they were not allowed to be declared final?

First: We need to understand the primary goal for taking the decision of adding default methods. It was interface evolution and not turning them into traits. The idea behind a default method is that it is an interface method which will be used if the derived class does not provide any specific implementation for it. If the derived class provides one then in that case the specific implementation will be used.

Again the purpose of default methods is interface evolution, and if a default method is allowed to be declared final then that would not be the default implementation rather it would be the only implementation.

Second: another use case is of diamond problem I explained where both classes (Engine and CNGKit) were having same default method start. Suppose if class CNGKit makes its default method final and class Engine is not under our control (may be third party or any other reason) then our class C is irretrievably broken because it cannot compile without overriding start() but it cannot because start() is final in CNGKit. Actually final methods make more sense for single-inheritance classes than for interfaces which can lead to multiple inheritance.

Third: The default implementation of a default method is only considered if the class (or superclass) does not provide any (abstract or concrete) declaration of the method. If a default method were declared final but one of the super-classes already implemented it then this default method will be ignored. And that is probably not the intention when we were trying to mark the default method final.

Default methods were meant for API evolution but is it wrong to use them in new interfaces?
IMO every feature of a language is right if its used in a right manner. Suppose we are having certain convenience methods which are generally implemented as non-default methods by all the classes. Consider an example of logging which has a convenience method to log and is implemented by each class. This method can be declared as default in an interface ILoggable and every class can implement it.
public interface ILoggable {
    default Logger logger() {
        return LoggerFactory.getLogger(this.getClass());
    }
}

In this example the method is strictly for convenience and can be an ideal fit for default method. As explained by Brian Goetz, the default methods fit the following use cases:
  1. Interface Evolution: We all agree to it.
  2. Optional methods: Implementors need not to implements these methods if they can live with default implementation e.g. Iterator.remove method is given a default and most of the implementations of Iterator have this behavior.
  3. Convenience methods: The methods that are strictly for convenience and again they are implemented as non-default methods on the class. An example is of logger given above.
  4. Combinators: These are the methods which create new instances of an interface based on the current instance. The examples are methods Predicate.and() and Comparator.thenComparing()
Why default methods cannot be declared synchronized?
Actually we cannot mark any method (default/static or whatever) in an interface synchronized. We can have a synchronize block within the default (or static) method but marking the method itself synchronized is not allowed.

Locking is all about coordinating the shared access to a mutable state. Each object should have a synchronization policy to figure out which lock guards the state of this object.  Many objects use Java Monitor Pattern where the state of the object is guarded by the intrinsic lock. The synchronized keyword on a method implicitly assumes this pattern. So the synchronization policy is determined by the state and state is owned by the class and not by an interface. If we use synchronized method in an interface it assumes a particular synchronization policy but we have no reasonable basis for assuming this. It would give us false sense of thread-safety and no error message would tell us that we are assuming wrong synchronization policy.

It is already tough to maintain synchronization policy for a single source file, for interfaces we can have multiple classes implementing it and in that case making sure that a subclass follows the synchronization policy defined by super class would be really hard.

Why synchronized cannot be used with a regular method?
Actually synchronized is an implementation detail. One implementation of the method might need to make the method synchronized where as the other one might not. The caller of the method does not care whether the method is synchronized or not. It is not part of the contract which explains what the method does. So synchronized does not belong to an interface rather to the concrete implementation of the interface.

Why we cannot define a default method for a method from Object class?
In some scenarios it may seem good idea to define default version of methods like toStream from Object class. But this is not allowed. This mail covers a lot of interesting stuff on this subject. There were many design decisions:
To keep interface model simple.
Inheriting  the methods equals/hashCode/toString is strongly tied to single inheritance and interfaces support multiple inheritance and are also stateless.
It can lead to some surprising behaviors.

When we talk about inheritance and conflict resolutions rules are simple:
  1. Classes win over interfaces.
  2. Derived interfaces win over super interfaces. A default from List wins over a default from Collection.
The methods (equals/hashCode/toString) are all about state of the object and the class owns the state, not the interface. And class is in a better position to determine what equality means for the class (see Effective Java for equality). Defaults should act as defaults only they should not change the semantics of concrete implementing classes. But in this case it wont be true.

Why default methods cannot be static?
First think why would you really need them? Default methods provide default implementation which can be overridden by implementation classes, if we declare them static will it be possible? And if you have the intention that the method cannot be overridden by implementation classes then why don't we mark it static all the way?

With advent of default methods they seem like abstract class only. Why were these methods added in first place?
To check the difference between abstract class and interface check this post. The motivation for them is already explained and a good example for them is we can use
list.sort(ordering);
instead of
Collections.sort(list, ordering);

That's all for now. Enjoy!

No comments: