Skip to content
Part IA Michaelmas Term

Abstract Classes and Interfaces

Abstract Classes

An abstract class is declared with the abstract keyword. It cannot be instantiated directly — the expression new AbstractClass() is a compile error.

An abstract class may include:

  • Concrete methods — methods with a body, providing implementation shared by all subclasses.
  • Abstract methods — methods declared with a signature but no body (ending with a semicolon). Subclasses must provide implementations of all abstract methods in order to become concrete (instantiable) classes.
abstract class Shape {
    protected String colour;

    public Shape(String colour) {
        this.colour = colour;
    }

    public String getColour() {        // concrete — shared implementation
        return colour;
    }

    public abstract double area();     // abstract — each shape calculates differently
    public abstract double perimeter();
}

A subclass that does not implement all inherited abstract methods must itself be declared abstract:

abstract class Quadrilateral extends Shape {
    public Quadrilateral(String colour) { super(colour); }

    @Override
    public double perimeter() { return 0; }  // implement one...

    // area() still abstract — so Quadrilateral is still abstract
}

class Rectangle extends Quadrilateral {
    private double width, height;

    public Rectangle(String colour, double w, double h) {
        super(colour);
        this.width = w;
        this.height = h;
    }

    @Override
    public double area() { return width * height; }

    @Override
    public double perimeter() { return 2 * (width + height); }
}

Why Abstract Classes?

Abstract classes are the right tool when:

  • Subclasses share common state (fields) — the abstract class can hold those fields and provide constructor logic.
  • Subclasses share common behaviour (concrete methods) — these live in the abstract class.
  • But each subclass must provide its own version of some specific behaviour — these are the abstract methods.

This is the template method pattern at the class-design level: the abstract class defines the structure and the subclasses fill in the details.

Interfaces

An interface declares a contract — a set of method signatures that implementing classes must provide. A class uses the implements keyword (not extends) to adopt an interface:

interface Drawable {
    void draw(Graphics g);
    boolean contains(Point p);
}

class Circle extends Shape implements Drawable {
    @Override
    public void draw(Graphics g) { /* rendering code */ }

    @Override
    public boolean contains(Point p) { /* hit-test code */ }

    @Override
    public double area() { return Math.PI * radius * radius; }

    @Override
    public double perimeter() { return 2 * Math.PI * radius; }
}

A class can implement any number of interfaces — multiple interface inheritance is allowed, unlike multiple class inheritance.

Interface Members

By default, all methods in an interface are:

  • public (you cannot have a non-public method in an interface, except private helper methods since Java 9)
  • abstract (unless marked default or static)

Fields in an interface are implicitly public static final — they are constants, not instance variables:

interface Constants {
    double PI = 3.14159;    // implicitly public static final
    int MAX_SIZE = 100;
}

Interfaces have no constructors, no instance fields, and no private state. They are purely behavioural contracts.

Default Methods (Java 8+)

Since Java 8, interfaces can include default methods — methods with a body, providing a default implementation that implementing classes inherit. A class can override the default if it needs different behaviour:

interface Logger {
    void log(String message);

    default void logError(String message) {
        log("ERROR: " + message);  // delegates to abstract log()
    }
}

Default methods were introduced primarily to allow evolution of existing interfaces without breaking existing implementations. If you add a new abstract method to a widely-used interface, every implementing class would need updating. Adding it as a default method avoids this.

Static Methods in Interfaces (Java 8+)

Interfaces can also contain static methods, which are called on the interface itself:

interface MathUtils {
    static double clamp(double value, double min, double max) {
        return Math.max(min, Math.min(value, max));
    }
}

double c = MathUtils.clamp(3.7, 0.0, 1.0);  // 1.0

These replace the common pattern of companion utility classes.

Private Methods in Interfaces (Java 9+)

Interfaces can have private methods (both instance and static) to share code between default methods without exposing it:

interface Logger {
    void log(String message);

    default void logWarning(String message) {
        log(formatMessage("WARN", message));
    }

    default void logError(String message) {
        log(formatMessage("ERROR", message));
    }

    private String formatMessage(String level, String message) {
        return "[" + level + "] " + message;
    }
}

Choosing: Abstract Class or Interface?

CriterionAbstract ClassInterface
Shared state (fields)YesNo (only constants)
ConstructorsYesNo
Access modifiers on methodsAnyImplicitly public
Single vs multiple inheritanceOne only (extends)Many (implements)
Default implementationsYes (concrete methods)Yes (default methods, Java 8+)
Semantic relationshipIs-aCan-do / is-capable-of

Use an abstract class when the subtypes form a tight family that shares state and implementation. Think of it as providing a partial implementation that subclasses complete.

Use an interface when you are defining a capability that can be adopted by unrelated classes — a Comparable can be any type of object that knows how to compare itself to another; a Runnable can be any object that can be executed. These concepts cut across class hierarchies.

The Key Insight

A memorable way to think about the distinction from the course:

  • extends is for a hierarchy you understand fully — you control or deeply understand the superclass. You are committing to inheriting its implementation and being tightly coupled to it.
  • implements is for a contract that many unrelated classes might need to fulfil. You are not inheriting code (or only via default methods); you are promising to provide specific methods.

This distinction guides large-scale design: prefer interfaces for public APIs and inter-component boundaries; use abstract classes for shared implementation within a component.