3.2 Static and Default Methods
In earlier versions of Java, all methods of an interface had to be abstract—that is, without a body. Nowadays you can add two kinds of methods with a concrete implementation: static and default methods. The following sections describe these methods.
3.2.1 Static Methods
There was never a technical reason why an interface could not have static methods, but they did not fit into the view of interfaces as abstract specifications. That thinking has now evolved. In particular, factory methods make a lot of sense in interfaces. For example, the IntSequence interface can have a static method digitsOf that generates a sequence of digits of a given integer:
IntSequence digits = IntSequence.digitsOf(1729);
The method yields an instance of some class implementing the IntSequence interface, but the caller need not care which one it is.
public interface IntSequence { ... public static IntSequence digitsOf(int n) { return new DigitSequence(n); } }
3.2.2 Default Methods
You can supply a default implementation for any interface method. You must tag such a method with the default modifier.
public interface IntSequence { default boolean hasNext() { return true; } // By default, sequences are infinite int next(); }
A class implementing this interface can choose to override the hasNext method or to inherit the default implementation.
An important use for default methods is interface evolution. Consider for example the Collection interface that has been a part of Java for many years. Suppose that way back when, you provided a class
public class Bag implements Collection
Later, in Java 8, a stream method was added to the interface.
Suppose the stream method was not a default method. Then the Bag class no longer compiles since it doesn’t implement the new method. Adding a nondefault method to an interface is not source-compatible.
But suppose you don’t recompile the class and simply use an old JAR file containing it. The class will still load, even with the missing method. Programs can still construct Bag instances, and nothing bad will happen. (Adding a method to an interface is binary-compatible.) However, if a program calls the stream method on a Bag instance, an AbstractMethodError occurs.
Making the method a default method solves both problems. The Bag class will again compile. And if the class is loaded without being recompiled and the stream method is invoked on a Bag instance, the Collection.stream method is called.
3.2.3 Resolving Default Method Conflicts
If a class implements two interfaces, one of which has a default method and the other a method (default or not) with the same name and parameter types, then you must resolve the conflict. This doesn’t happen very often, and it is usually easy to deal with the situation.
Let’s look at an example. Suppose we have an interface Person with a getId method:
public interface Person { String getName(); default int getId() { return 0; } }
And suppose there is an interface Identified, also with such a method.
public interface Identified { default int getId() { return Math.abs(hashCode()); } }
You will see what the hashCode method does in Chapter 4. For now, all that matters is that it returns some integer that is derived from the object.
What happens if you form a class that implements both of them?
public class Employee implements Person, Identified { ... }
The class inherits two getId methods provided by the Person and Identified interfaces. There is no way for the Java compiler to choose one over the other. The compiler reports an error and leaves it up to you to resolve the ambiguity. Provide a getId method in the Employee class and either implement your own ID scheme, or delegate to one of the conflicting methods, like this:
public class Employee implements Person, Identified { public int getId() { return Identified.super.getId(); } ... }
Now assume that the Identified interface does not provide a default implementation for getId:
interface Identified { int getId(); }
Can the Employee class inherit the default method from the Person interface? At first glance, this might seem reasonable. But how does the compiler know whether the Person.getId method actually does what Identified.getId is expected to do? After all, it might return the level of the person’s Freudian id, not an ID number.
The Java designers decided in favor of safety and uniformity. It doesn’t matter how two interfaces conflict; if at least one interface provides an implementation, the compiler reports an error, and it is up to the programmer to resolve the ambiguity.