Interfaces vs Abstract Classes
Default, static and private interface methods, the diamond problem, functional interfaces — and how to choose between interface and abstract class.
Interfaces
An interface is a contract: a set of method signatures a class promises to implement. Since Java 8 interfaces can also carry behaviour:
| Member | Since | Notes |
|---|---|---|
| Abstract methods | 1.0 | Implicitly public abstract |
| Constants | 1.0 | Implicitly public static final |
default methods |
8 | Implementation inherited by implementors; can be overridden |
static methods |
8 | Called as Interface.method(); not inherited |
private methods |
9 | Share code between default methods |
public interface Shape {
double area(); // abstract
default String describe() { // default
return "%s with area %.2f".formatted(name(), area());
}
static Shape unitSquare() { return () -> 1.0; } // static factory
private String name() { // private helper
return getClass().getSimpleName();
}
}
Abstract classes
An abstract class is a partially implemented class that can’t be instantiated. Unlike interfaces, it can have instance state, constructors, and methods with any access level.
public abstract class HttpHandler {
private final MeterRegistry metrics; // state
protected HttpHandler(MeterRegistry m) { this.metrics = m; }
public final Response handle(Request r) { // template method
metrics.counter("requests").increment();
return doHandle(r);
}
protected abstract Response doHandle(Request r);
}
How to choose
| Interface | Abstract class | |
|---|---|---|
| Multiple inheritance | A class can implement many | A class can extend only one |
| State (instance fields) | No | Yes |
| Constructors | No | Yes |
| Method access | public (or private helpers) |
Any |
| Relationship | can-do (capability) | is-a (shared identity) |
| Evolving the API | Add default methods |
Add concrete methods |
The diamond problem
If a class inherits the same default method from two interfaces, the compiler forces it to resolve the conflict explicitly:
interface Camera {
default String capture() { return "photo"; }
}
interface Phone {
default String capture() { return "voice memo"; }
}
class Smartphone implements Camera, Phone {
// Without this override: "class Smartphone inherits unrelated defaults for capture()"
@Override
public String capture() {
return Camera.super.capture() + " + " + Phone.super.capture();
}
}
abstract class Device {
public String capture() { return "device capture"; }
}
class ActionCam extends Device implements Camera {
// No conflict: a class method always wins over an interface default ("class wins" rule)
}
class Main {
public static void main(String[] args) {
System.out.println(new Smartphone().capture());
System.out.println(new ActionCam().capture());
}
}The resolution rules:
- Classes win — a method in the superclass chain takes priority over any interface default.
- More specific interfaces win — if
B extends Aand both define the default,B’s is used. - Otherwise the class must override and may delegate with
X.super.method().
Functional interfaces
An interface with exactly one abstract method is a functional interface and can be implemented with a lambda. @FunctionalInterface makes the compiler enforce the single-abstract-method rule.
@FunctionalInterface
interface Validator<T> {
boolean validate(T value);
default Validator<T> and(Validator<T> other) { // defaults don't count
return v -> validate(v) && other.validate(v);
}
}
Validator<String> notBlank = s -> !s.isBlank();
Validator<String> shortEnough = s -> s.length() <= 20;
boolean ok = notBlank.and(shortEnough).validate("Ada");
See Lambdas & Functional Interfaces for the built-in ones (Function, Predicate, Supplier, Consumer…).
Sealed interfaces
Since Java 17 you can restrict which classes may implement an interface with sealed … permits. This makes hierarchies closed and enables exhaustive pattern matching — see Sealed Classes & Pattern Matching.
public sealed interface Payment permits Card, BankTransfer, Wallet {}
Marker interfaces
Interfaces with no methods — Serializable, Cloneable, RandomAccess — tag a class with a capability checked at runtime via instanceof. Modern code often uses annotations instead, but marker interfaces have the advantage of being checked by the type system.
Mark it complete to track your progress through Modern Java.