Skip to content

8 · 32 · 5 min

Two ways to say what something must do

Both let you program against a promise rather than a concrete type, and they answer different questions.

An interface is a contract

It says what an implementer must provide and nothing about how.

java
public interface PaymentMethod {
    Receipt charge(Money amount);
    boolean supports(Currency currency);
}

A class may implement many interfaces, because implementing several contracts is not contradictory.

An abstract class is a partial implementation

It provides shared code and leaves specific parts to subclasses.

java
public abstract class Exporter {
    public final void export(Report r) {     // the shared sequence
        validate(r);
        write(format(r));
        audit(r);
    }
    protected abstract String format(Report r);   // subclasses supply this
}

A class may extend only one, because inheriting state from two parents raises the question of which copy you have.

Choosing

WantUse
A capability several unrelated types can haveinterface
Shared implementation among related typesabstract class
Several of them on one classinterface
To hold stateabstract class

The relationship reading helps: an interface is usually "can do", an abstract class is usually "is a kind of". A Bird and a Drone are not related and can both be Flyable.

Default methods blur the line

Java 8 allowed interfaces to carry implementations:

java
public interface Collection<E> {
    boolean removeIf(Predicate<? super E> filter);   // has a body
}

The purpose was compatibility: adding a method to an interface would otherwise break every existing implementation. With a default, existing classes keep compiling.

Interfaces still cannot hold instance state, which remains the real difference.

Why interfaces dominate in practice

Two reasons, both practical rather than philosophical.

Testing. A dependency typed as an interface can be substituted with a fake. A dependency typed as a concrete class usually cannot.

Coupling. Depending on an abstraction means the implementation can be replaced without touching the caller, which is the dependency inversion principle stated concretely.

The habit worth forming: type parameters and fields to the interface, not the implementation.

java
List<String> names = new ArrayList<>();       // swap for LinkedList freely
ArrayList<String> names = new ArrayList<>();  // now the choice is baked in