Even though interfaces and abstract classes have many similarities at first look, especially after introducing
default methods, they have different use cases and capabilities.
Note: Java 8 is assumed, but the Java 10 feature Local Variable Type Inference is used if appropriate to increase readability.
If a class implements an interface, it’s a behavioral contract of how an instance interacts with the world around it. Usually, there are method signatures present, but an empty “marker” interface is possible too.
Think of a simple alarm clock with a single alarm:
Any type implementing
AlarmClock must implement the methods to conform to the interface.
Java 8 improved interfaces by introducing
default methods, allowing us to provide a default implementation of a method that mustn’t be overridden:
Concrete implementations no longer need to provide a snooze-logic, but can still choose to override one or both snooze-methods.
Also introduced with Java 8 were
static interface methods:
static methods provide more cohesion by aggregating related methods/logic in a single place without needing a helper object, like an
default methods, we can’t override
abstract class is a special kind of non-instantiable class that can be partially implemented.
They are designed to be completed by another class.
By defining a method signature
abstract, the method body must be omitted, like in an
We can even replicate our
interface AlarmClock easily:
An abstract class’s most significant advantage over interfaces is being able to use state and provide constructors.
Like any other class, we can define an internal state and thereby include more logic than with
We can also declare constructors in an
If and what kind of constructors are declared directly influence how we must call them in any subtype.
Without any explicit constructors, we don’t have to call the no argument default constructor in any subtype.
If a no argument default exists in an
abstract class, there’s still no need to call it explicitly in any inheriting type; t the call will be made “behind the scenes”.
For example, our
AlarmClock could use an
Optional<LocalTime>, and initialize it in the constructor:
Like with inheritance between “normal” classes, the existence of the default no-arg constructor depends on parameterized constructors’ presence.
If parameterized constructors exist but not an explicit default no-arg one, any inheriting type must call a
super constructor in its own constructor.
Let’s say we want to set the snooze duration in a constructor:
The concrete implementation now needs an explicit constructor calling
super to make the compiler happy.
It can be a default no-arg constructor, or parameterized one.
If multiple constructors exist in the base type, calling any of them will do:
Interface or Abstract Class?
Even though both provide an overlapping set of capabilities, their intended use and applications differ.
Java, unlike other languages, doesn’t support multi-inheritance.
A class can only extend a single other class, like
Interfaces, on the other hand, do not succumb to this restriction. A class can implement multiple interfaces, and an interface can extend numerous interfaces.
default methods will be inherited, leading to a problem: what if multiple interfaces implement the same method?
Without an explicit
@Override, the compiler won’t know which default method implementation to call, so we’re forced to override it:
But we can still rely on the default implementations by leveraging
Another option is to extend the interface itself:
interface, everything is implicitly
public, and only
static methods are allowed to be
With the lack of state, this restriction makes sense. Common logic used by
default methods can be safely refactored to a
private static method to hide it from the implementer.
protected, an interface can never only provide a method to its direct implementer that isn’t available to all other types.
The only available visibility restriction is type-based. If the interface declaration itself isn’t declared public, it will only be available on a package-level.
Abstract classes are, well, classes, so they can leverage the whole spectrum of visibility modifiers. When inherited, they obey the same rules: overridden methods must have at least the same visibility but can choose to increase visibility.
Visibility Increments (none = package) -> protected -> public
Before the introduction of interface
default methods, abstract classes were the preferred way to ensure extensibility.
If we added a new method to an interface, all of its implementers were forced to implement it, too, most likely breaking a lot of code. On the other hand, an abstract class could easily add a non-abstract method, and all related types were fine.
default methods, we can finally improve interfaces by adding new methods without breaking existing types.
Actually, JDK 8 implements a lot of additional features with
default methods, like
Even if no default logic is possible, throwing an
UnsupportedOperationException is better than breaking every related type.
Why not use both?
Instead of using either an interface or an abstract class, we can also use them both in tandem. An interface can describe the public contract of a type, and an abstract base class provides a starting point to implement it. That’s especially useful for the service locator pattern.
My company uses this kind of combination for our data access objects.
DAO interface describes how to access data, and an abstract class
HibernateDAO provides the base functionality to access data with Hibernate.
Here’s a simplified example:
Now an actual
DAO service interface can just extend
DAO, and its concrete implementation can extend
Thanks to this construction, we achieved the following:
DAOis the minimum contract any DAO must fulfill.
HibernateDAOprovides the most basic functionality any concrete Hibernate-based
- Standard-DAOs, like
BeanDAOand its concrete implementation, only need a minimal amount of code.
- Mocking for testing is more straightforward, too. Just like
HibernateDAO, we can create a shared abstract class
Contract vs. Shared Logic
The best way to decide which to use is to realize what their original purpose is.
An interface is basically a contract describing the capabilities of type.
The multiple inheritances of itself and its implementers allow a wide range of use, from simple marker-interfacer, or bundles of contracts to be followed.
The addition of
default methods even allows us to provide logic to all implementers and make extensibility possible.
Abstract classes are more than just contracts. They are partially implemented types, sometimes not even providing any implementation at all. This allows the creation of base classes, sharing logic and implementation details, to be completed by the inheritors.
What Is an Interface? (Oracle)
Abstract Methods and Classes (Oracle)
Interfaces (Java Language Specification)
Abstract Classes (Java Language Specification)