jario_
Back to overview

Chapter 9

Inheritance and interfaces

A suite is a hotel room, just more expensive. Instead of writing everything again, it inherits from the room and only changes what's different. Interfaces go one step further: they only define what a class must be able to do, not how. Both let you write code that treats many different classes the same way.

Inheritance with extends

class Suite extends Room means: a suite has all the fields and methods of a Room. Room is the superclass, Suite the subclass.

  • The subclass constructor calls the superclass constructor with super(...) in its first line.
  • You can override a method of the superclass: same name, same parameters, new body. @Override above it makes the compiler check that the method really exists up there.
  • super.price() calls the superclass version. That way you extend it instead of copying the formula.
  • The subclass can't see private fields of the superclass directly. It uses the getters, like getNights().

A variable of type Room may also hold a Suite. Which price() runs is decided by the object, not by the type of the variable. That's why describe() uses the higher price for the suite, even though describe() is only written in Room. This is called polymorphism.

class Room {
    private final int nights;

    public Room(int nights) {
        this.nights = nights;
    }

    public int getNights() {
        return nights;
    }

    public double price() {
        return 80.0 * nights;
    }

    public String describe() {
        return nights + " nights: " + price() + " €";
    }
}

class Suite extends Room {
    public Suite(int nights) {
        super(nights);
    }

    @Override
    public double price() {
        return super.price() + 50.0 * getNights();
    }
}

public class Main {
    public static void main(String[] args) {
        Room room = new Room(2);
        Room suite = new Suite(2);
        System.out.println(room.describe());
        System.out.println(suite.describe());
    }
}

Abstract classes and interfaces

An abstract class (abstract class Vehicle) is only meant as a superclass. You can't create an object of it directly. An abstract method has no body, just the header with a semicolon. Every subclass must implement it, otherwise it doesn't compile.

An interface contains only method headers: a contract. A class fulfils it with implements and writes out all the methods. A class can extend only one superclass, but implement as many interfaces as it likes: class Lamp implements Switchable, Dimmable.

Methods and lists that only know the general type (List<Vehicle>, PaymentMethod method) work with every subclass. Whether an object also implements an interface is checked with instanceof: if (v instanceof Electric e) gives you the object as e right away.

import java.util.ArrayList;
import java.util.List;

abstract class Vehicle {
    private final String name;

    public Vehicle(String name) {
        this.name = name;
    }

    public abstract int wheels();

    public String describe() {
        return name + " has " + wheels() + " wheels";
    }
}

interface Electric {
    int range();
}

class Bicycle extends Vehicle {
    public Bicycle() {
        super("Bicycle");
    }

    @Override
    public int wheels() {
        return 2;
    }
}

class ElectricCar extends Vehicle implements Electric {
    public ElectricCar() {
        super("Electric car");
    }

    @Override
    public int wheels() {
        return 4;
    }

    @Override
    public int range() {
        return 400;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Vehicle> fleet = new ArrayList<>();
        fleet.add(new Bicycle());
        fleet.add(new ElectricCar());
        for (Vehicle v : fleet) {
            System.out.println(v.describe());
            if (v instanceof Electric e) {
                System.out.println("  range: " + e.range() + " km");
            }
        }
    }
}

Sorting with Comparable

Collections.sort(list) sorts a list if its elements are Comparable. Numbers and strings already are; your own classes implement Comparable<Runner> for it. Its only method compareTo(other) returns:

  • a negative number if this comes before other,
  • a positive number if it comes after,
  • 0 if both are equal.

Integer.compare(a, b) returns exactly that for two numbers. For descending order, swap the arguments. For a tie, compare a second property afterwards, such as the name with name.compareTo(other.name).

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class Runner implements Comparable<Runner> {
    private final String name;
    private final int seconds;

    public Runner(String name, int seconds) {
        this.name = name;
        this.seconds = seconds;
    }

    @Override
    public int compareTo(Runner other) {
        return Integer.compare(seconds, other.seconds);
    }

    @Override
    public String toString() {
        return name + " (" + seconds + " s)";
    }
}

public class Main {
    public static void main(String[] args) {
        List<Runner> runners = new ArrayList<>();
        runners.add(new Runner("Mia", 52));
        runners.add(new Runner("Leo", 48));
        runners.add(new Runner("Ana", 55));
        Collections.sort(runners);
        System.out.println(runners);

        System.out.println("Ada".compareTo("Bob"));
        System.out.println("Bob".compareTo("Ada"));
        System.out.println("Ada".compareTo("Ada"));
    }
}

Common mistakes

  • super(...) missing

    If the superclass only has a constructor with parameters, every subclass must call it with super(...), in the first line. Otherwise the compiler reports constructor Room in class Room cannot be applied to given types.

  • Overriding without @Override

    A typo like public double prize() or a different parameter type silently creates a new method that is never called. With @Override, the compiler reports the mistake right away.

  • Declaring a field again in the subclass

    If the subclass declares a field with the same name as the superclass, the object then has two of them. The superclass methods only see their own:

    class Suite extends Room {
        private int nights; // hides Room's nights, stays 0
    }

    Fields belong only in the class that introduces them.

  • Object of an abstract class

    The compiler rejects new Vehicle("Bus") with Vehicle is abstract; cannot be instantiated. Create an object of a subclass instead; you can still store it in a variable of type Vehicle.