skip to content
everything.oop.abstractClassesAndInterfaces

abstract classes and interfaces

default methods, the diamond problem, and when to use which.

chapter
chapter 03.08
reading time
7 min read

an abstract class is a half-finished class: it can have fields, constructors and real methods, plus abstract ones the subclass must fill in. you can extend only one. an interface is a contract with no instance state: abstract methods, plus default, static and private methods. a class can implement as many as it likes.

when two interfaces hand a class the same default method, three rules decide who wins: the class wins, then the more specific interface wins, else it's a compile error and you pick with X.super.m().

the picture

an abstract class is a house with the foundations poured and the walls up, missing a kitchen. you buy the house (extend it), and you're stuck with its layout, but you got a lot for free.

an interface is a job description: "must be able to area()". anybody can apply, a circle, a square, a lambda, and you can hold several job titles at once (Comparable, Serializable, Shape). since java 8 a job description can even come with a default way of doing some of the tasks.

an abstract class: state plus holes

Main.java
abstract class Report {
    private final String title;           // state: interfaces can't have this
 
    Report(String title) {                // a constructor: neither can they
        this.title = title;
    }
 
    final String render() {               // the fixed recipe
        return "== " + title + " ==\n" + body() + "\n-- end";
    }
 
    abstract String body();               // the hole each subclass fills
}
 
class Sales extends Report {
    Sales() { super("sales"); }
 
    @Override
    String body() { return "42 units"; }
}
 
void main() {
    IO.println(new Sales().render());
}
output
== sales ==
42 units
-- end

that shape (a final method that calls an abstract one) is the template method pattern. new Report("x") won't compile: error: Report is abstract; cannot be instantiated. it has a constructor anyway, because every subclass object contains a Report part that needs initializing (see inheritance).

an interface: a contract with optional defaults

Main.java
interface Shape {
    double area();                               // abstract: every Shape must say
 
    default String describe() {                  // default: a body you may override
        return "a shape of area " + round(area());
    }
 
    private static double round(double d) {      // private helper, java 9+
        return Math.round(d * 100) / 100.0;
    }
 
    static Shape square(double side) {           // static: called as Shape.square(..)
        return () -> side * side;
    }
}
 
record Circle(double r) implements Shape {
    public double area() { return Math.PI * r * r; }
}
 
void main() {
    IO.println(new Circle(1).describe());
    IO.println(Shape.square(3).describe());
}
output
a shape of area 3.14
a shape of area 9.0

notice Shape.square returns a lambda. Shape has exactly one abstract method, so it's a functional interface, and any lambda with the right shape is a Shape.

can it have...abstract classinterface
instance fieldsyesno. fields are implicitly public static final constants
constructorsyesno
abstract methodsyesyes (the default, no keyword needed)
concrete methodsyes, any visibilitydefault (public), static, private
how many per classone (extends)any number (implements)
non-public membersyesonly private methods
static methods inherited by implementersyesno: always call them as Shape.square()

why default methods at all? java 8 needed to add stream() and forEach() to Collection without breaking every class that ever implemented it. a default method is a body that implementers get unless they write their own. that solved interface evolution, and created a new problem.

the diamond problem

if Robot gets hello() from two places, which one runs? java's answer is three rules, applied in order:

  1. class wins. a method from the superclass chain (even an abstract one) beats every interface default.
  2. more specific wins. if one interface extends the other, the sub-interface's default replaces its parent's.
  3. otherwise, you choose. two unrelated defaults is a compile error until the class overrides the method. inside the override, Greeter.super.hello() calls one specific default.
case =

rule 3: two unrelated defaults, neither more specific. javac refuses to guess.

interface Greeter {
    default String hello() { return "Greeter.hello"; }
}

interface Waver {
    default String hello() { return "Waver.hello"; }
}

class Robot implements Greeter, Waver {}
$ javac
error: types Greeter and Waver are incompatible; class Robot inherits unrelated defaults for hello() from types Greeter and Waver
pick a case. green is the method that wins, red is where javac gives up, faded is a loser. every case was compiled on jdk 25, and the output or error is copied from javac.

there's no state in interfaces, so this diamond is only ever about which code, never about "how many copies of a field". that's exactly why java allows multiple inheritance of interfaces but not classes.

predict the output
java
interface Greeter {
    default String hello() { return "Greeter.hello"; }
}
 
class Base {
    public String hello() { return "Base.hello"; }
}
 
class Robot extends Base implements Greeter {}
 
void main() {
    IO.println(new Robot().hello());
}

which one should i use?

you need...use
a type that unrelated classes can all be (Comparable, Runnable)interface
a class to be several things at onceinterfaces
to add a capability to existing classes (a mixin)interface
a lambda to be able to implement itinterface (functional)
shared state or a constructor that every subtype needsabstract class
non-public helper methods for subclasses (protected)abstract class
the convenience of bothan interface, plus an abstract "skeletal" class that implements it

that last row is how the JDK does it: List is the interface (what you program against), AbstractList is the skeletal implementation (implement get and size and you get the rest), and ArrayList extends it. you depend on List, never on AbstractList.

  • interface
  • └── interface
  • ├── interface: what you program against
  • │ └── abstract class: skeletal implementation
  • │ └── ArrayLista real class (it also says implements List)
  • └── interface
  • └── abstract class
  • └── HashSet
the collections framework uses both: interfaces for the types you declare variables with, abstract skeletal classes so implementers write less. click to fold.
keylines
  • abstract class: state, constructors, holes. extend one.
  • interface: a contract, no instance state. implement many.
  • default methods are fallbacks. a class method always beats them.
  • more specific interface beats less specific. unrelated defaults: override and pick with X.super.m().
  • program to the interface, inherit from the skeletal class.