Skip to content

Interfaces and Inheritance

Scope: extends, implements, abstract classes, and what interfaces are actually for. All samples were compiled and run with OpenJDK 21.

Inheritance: extends

A class can extend another, inheriting its fields and methods:

class Animal {
    String name;

    Animal(String name) {
        this.name = name;
    }

    void speak() {
        System.out.println(name + " makes a sound");
    }
}

class Dog extends Animal {
    Dog(String name) {
        super(name);   // calls Animal's constructor — required first
    }

    @Override
    void speak() {
        System.out.println(name + " says woof!");
    }
}

Dog is an Animal with a better speak(). @Override is a promise to the compiler: "I'm replacing the parent's method — error if I misspelled it." Always use it.

Interfaces: implements

An interface is a contract: "any class implementing me provides these methods." No implementation, just promises:

interface Swimmer {
    void swim();   // no body — implementing classes fill it in
}

class Duck extends Animal implements Swimmer {
    Duck(String name) {
        super(name);
    }

    @Override
    public void swim() {
        System.out.println(name + " paddles");
    }
}

A class extends one parent but can implement many interfaces — that's how Java gets most of multiple inheritance's benefits without its messes.

Why interfaces matter

They let code depend on abilities, not concrete types:

static void race(Swimmer s) {
    s.swim();
}

race accepts a Duck, a Fish, a Human — anything that swims. The Collections chapter leans on this: List is an interface, ArrayList one implementation of it.

Abstract classes

Halfway between: a class you can't instantiate, meant to be extended, which can hold shared code:

abstract class Shape {
    abstract double area();   // subclasses must define this

    void describe() {         // but shared code lives here
        System.out.println("Area: " + area());
    }
}

class Circle extends Shape {
    double radius;

    Circle(double radius) {
        this.radius = radius;
    }

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

Use an abstract class when subclasses share code; an interface when they share only a contract.

Putting it together

public class Zoo {
    public static void main(String[] args) {
        Duck d = new Duck("Daffy");
        d.speak();   // inherited from Animal... but wait
        d.swim();    // from Swimmer
        Circle c = new Circle(2.0);
        c.describe();
    }
}

Check: prints

Daffy makes a sound
Daffy paddles
Area: 12.566370614359172

(Duck didn't override speak(), so Animal's version runs — inheritance in action.)

Next: Exceptions — how Java handles things going wrong.