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
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());
}== 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
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());
}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 class | interface |
|---|---|---|
| instance fields | yes | no. fields are implicitly public static final constants |
| constructors | yes | no |
| abstract methods | yes | yes (the default, no keyword needed) |
| concrete methods | yes, any visibility | default (public), static, private |
| how many per class | one (extends) | any number (implements) |
| non-public members | yes | only private methods |
| static methods inherited by implementers | yes | no: 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:
- class wins. a method from the superclass chain (even an abstract one) beats every interface default.
- more specific wins. if one interface extends the other, the sub-interface's default replaces its parent's.
- 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.
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 {}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.
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 once | interfaces |
| to add a capability to existing classes (a mixin) | interface |
| a lambda to be able to implement it | interface (functional) |
| shared state or a constructor that every subtype needs | abstract class |
non-public helper methods for subclasses (protected) | abstract class |
| the convenience of both | an 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
- 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.