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:
- static methods do not belong to interfaces. They belong to utility classes.
- "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 interfaceIx
, then the classCx
implementingIx
and having a privatefoo
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?
5 Answers 5
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
.
-
19C# overcomes this problem with Extension Methods.Robert Harvey– Robert Harvey03/20/2014 15:50:24Commented Mar 20, 2014 at 15:50
-
5and it allows a linked list to use a O(1) extra space and O(n log n) time mergesort, because linked lists can be merged in place, in java 7 it dumps to an external array and then sort thatratchet freak– ratchet freak03/20/2014 16:32:54Commented Mar 20, 2014 at 16:32
-
5I've found this paper where Goetz explains the problem. So i'll be marking this answer as the solution for now.Mister Smith– Mister Smith03/21/2014 10:01:59Commented Mar 21, 2014 at 10:01
-
1@RobertHarvey: Create two hundred-million-item List<Byte>, use
IEnumerable<Byte>.Append
to join them, and then callCount
, then tell me how extension methods solve the problem. IfCountIsKnown
andCount
were members ofIEnumerable<T>
, the return fromAppend
could advertiseCountIsKnown
if the constituent collections did, but without such methods that's not possible.supercat– supercat06/12/2014 22:30:37Commented Jun 12, 2014 at 22:30 -
6@supercat: I haven't the slightest idea what you are talking about.Robert Harvey– Robert Harvey06/12/2014 22:35:30Commented Jun 12, 2014 at 22:35
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.
-
1For further follow-up on my last paragraph (i.e. don't enforce contracts in interfaces), it's worth pointing out that
default
methods cannot overrideequals
,hashCode
andtoString
. A very informative cost/benefit analysis of why this is disallowed can be found here: mail.openjdk.java.net/pipermail/lambda-dev/2013-March/…ngreen– ngreen10/06/2014 17:13:42Commented Oct 6, 2014 at 17:13 -
It's too bad that Java only has a single virtual
equals
and a singlehashCode
method since there are two different kinds of equality which collections might need to test, and items which would implement multiple interfaces may be stuck with conflicting contractual requirements. Being able to use lists that aren't going to change ashashMap
keys is helpful, but there are also times when it would be helpful to store collections in ahashMap
which matched things based upon equivalence rather than current state [equivalence implies matching state and immutability].supercat– supercat10/11/2014 17:12:34Commented Oct 11, 2014 at 17:12 -
Well, Java sort of has a workaround with the Comparator and Comparable interfaces. But those are kind of ugly, I think.ngreen– ngreen10/14/2014 02:50:11Commented Oct 14, 2014 at 2:50
-
Those interfaces are only supported for certain collection types, and they pose their own problem: a comparator may itself potentially encapsulate state (e.g. a specialized string comparator might ignore a configurable number of characters at the start of each string, in which case the number of characters to ignore would be part of the comparator's state) which would in turn become part of the state of any collection which was sorted by it, but there's no defined mechanism for asking two comparators if they are equivalent.supercat– supercat10/14/2014 02:56:42Commented Oct 14, 2014 at 2:56
-
Oh yes, I get the pain of comparators. I'm working on a tree structure that ought to be simple but isn't because it's so hard to get the comparator right. I'm probably going to write a custom tree class just to make the problem go away.ngreen– ngreen10/14/2014 03:00:51Commented Oct 14, 2014 at 3:00
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.
-
4Counter-argument: since static methods, if written properly, are self-contained, they make more sense in a separate static class where they can be collected and categorized by class name, and less sense in an interface, where they are essentially a convenience that invites abuses like holding static state in the object or causing side effects, making the static methods untestable.Robert Harvey– Robert Harvey03/20/2014 16:23:32Commented Mar 20, 2014 at 16:23
-
3@RobertHarvey What's to stop you from doing equally silly things if your static method is in a class though? Also, that the method in the interface may not require any state at all. You might simply be trying to enforce a contract. Suppose you've got a
Stack
interface and you want to ensure that whenpop
is called with an empty stack, an exception is thrown. Given abstract methodsboolean isEmpty()
andprotected T pop_impl()
, you could implementfinal T pop() { isEmpty()) throw PopException(); else return pop_impl(); }
This enforces the contract on ALL implementors.Doval– Doval03/20/2014 16:44:00Commented Mar 20, 2014 at 16:44 -
Wait, what? Push and Pop methods on a stack are not going to be
static
.Robert Harvey– Robert Harvey03/20/2014 16:46:22Commented Mar 20, 2014 at 16:46 -
@RobertHarvey I would've been clearer if not for the character limit on comments, but I was making the case for default implementations in an interface, not static methods.Doval– Doval03/20/2014 16:47:57Commented Mar 20, 2014 at 16:47
-
8I think default interface methods are rather a hack that has been introduced to be able to extend the standard library without the need to adapt existing code based on it.Giorgio– Giorgio03/20/2014 17:37:46Commented Mar 20, 2014 at 17:37
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.
-
1Extension methods don't add functionality to interfaces. Extension methods are just syntactic sugar for calling static methods on a class using the convenient
list.sort(ordering);
form.Robert Harvey– Robert Harvey03/20/2014 16:15:12Commented Mar 20, 2014 at 16:15 -
If you look at the
IEnumerable
interface in C#, you can see how implementing extension methods to that interface (likeLINQ to Objects
does) adds functionality for every class that implementsIEnumerable
. That's what I meant by adding functionality.rae1– rae103/20/2014 16:24:41Commented Mar 20, 2014 at 16:24 -
2That's the great thing about extension methods; they give the illusion that you are bolting on functionality to a class or interface. Just don't confuse that with adding actual methods to a class; class methods have access to the private members of an object, extension methods don't (since they're really just another way of calling static methods).Robert Harvey– Robert Harvey03/20/2014 16:29:21Commented Mar 20, 2014 at 16:29
-
2Exactly, and that's why I see some relation on having static or default methods in an interface in Java; the implementation is based on what's available to the interface not the class itself.rae1– rae103/20/2014 16:46:35Commented Mar 20, 2014 at 16:46
The two main purposes I see in default
methods (some use cases serve both purposes):
- Syntax sugar. A utility class could serve that purpose, but instance methods are nicer.
- 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.
Explore related questions
See similar questions with these tags.
@Deprecated
category! static methods are one of the most abused constructs in Java, because of ignorance and laziness. Lots of static methods usually means incompetent programmer, increase coupling by several orders of magnitude and are a nightmare to unit test and refactor when you do realize why they are a bad idea!