Topic 10: Inheritance

Introduction

Inheritance allows us to use an existing class as a basis for a new, related class. Imagine we wanted to write classes representing different types of vehicle, e.g. Car, Bus, Motorbike. If we were to write the three classes entirely separately, we'd be repeating a good deal of code - e.g. the code for starting and stopping the engine is common to all three classes.

So what we could do in this case is create a Vehicle class, containing common functionality for all types of vehicle, and then inherit various subclasses of Vehicle (such as Car, Bus, and Motorbike) which provide additional functionality specific to that type of Vehicle. We can say that:

How is inheritance achieved in Java?

In Java, we use the keyword extends, e.g.

public class Car extends Vehicle
{
.... Car attributes and methods
}
This means that the Car class inherits from Vehicle. All attributes and methods in Vehicle will be inherited by Car, so when you create a Car, all the Vehicle methods and attributes will be available.

Java Inheritance Example

Below is a Vehicle superclass with Bike and Car subclasses.

Vehicle class (Vehicle.java):

// Generic Vehicle superclass
public class Vehicle 
{
    // Attributes common to all vehicles
    protected int topSpeed, nWheels; 
    protected String make;

    public Vehicle (String makeIn, int topSpeedIn, int nWheelsIn)
    {
        this.make=makeIn;
        this.topSpeed=topSpeedIn;
        this.nWheels=nWheelsIn;
    }

    public void move()
    {
        System.out.println("Moving along...");
    }

    public void print()
    {
        System.out.println( "Make : " + make + "\nTop speed: " + 
            topSpeed + "\nno. wheels: " + nWheels);
    }
}

Car class (Car.java):

public class Car extends Vehicle 
{
    // Car-specific data
    private int engineCapacity; 
    private boolean engineRunning;

    public Car(String makeIn, int topSpeedIn, int engineCapacityIn)
    {
        // Call the superclass (Vehicle) constructor to construct the Vehicle 
        // component of the Car. We will pass in the top speed, make, and
        // number of wheels (which we know will be 4)
        super(makeIn, topSpeedIn, 4);

        // Set up the Car-specific attributes (engine capacity)
        this.engineCapacity = engineCapacityIn;
    }

    // Overridden move() for cars
    public void move()
    {
        if (engineRunning)
        {
            System.out.println("Driving along...");
        }
        else
        {
            System.out.println
                ("Can't drive the car if the engine's stopped!!!");
        }
    }

    // Car-specific methods
    public void start()
    {
        engineRunning=true;
    }

    public void stop()
    {
        engineRunning = false;
    }

    // Convert the car to a String. Note the use of super.toString() to
    // call the superclass (Vehicle) version of toString() to get the
    // make, top speed and number of wheels.
    public void print()
    {
        super.print();
        System.out.println(" Engine running? 
                    " + engineRunning + "\nEngine capacity: " +
                            engineCapacity);
    }
}

Bike class (Bike.java):

public class Bike extends Vehicle 
{
    // Bike-specific data
    private boolean isOffRoad;
    private int nGears; 

    public Bike(String makeIn,int topSpeedIn,boolean isOffRoadIn,int nGearsIn)
    {
        //Call the superclass (Vehicle) constructor to construct the Vehicle
        // component of the Bike. We will pass in the top speed, make, and
        // number of wheels (which we know will be 2)
        super(makeIn,topSpeedIn,2);

        // Set up the Bike-specific attributes (off-road status and gears)
        this.isOffRoad = isOffRoadIn;
        this.nGears = nGearsIn;
    }

    public void print()
    {
       super.print();
       System.out.println(" Off road? " + 
                            isOffRoad + "\nNo. Gears: " +
                            nGears);
    }
}

public class InheritanceTestApp 
{
    public static void main (String args[])
    {
        Car c= new Car("Ford",120,2000);
        Bike b = new Bike("Raleigh",30,true,27);

        c.print();
        b.print();

        c.move();
        c.start();
        c.move();
        c.stop();

        b.move();
    }
}

How is this working?

Overriding Methods

In the example, Vehicle and Car both have a method called move(). We have overridden the original version in Vehicle with the version in Car - in other words, replaced the original Vehicle version of move() with a new version in Car. This will mean that:

In the same way, print() is overridden in both Car and Bike. However, unlike for move(), the overridden version of print() calls the original, Vehicle version of print() using the super keyword: super.print();.

Exercise

Basic inheritance exercise

  1. Create a class LibraryItem, representing an item that a library might lend out, such as a book, a DVD or a CD. Give your LibraryItem class a title attribute, representing the LibraryItem's title. It should also have a getDescription() method. This should just return the title. It should also have a suitable constructor.
  2. Inherit Book, DVD and CD from LibraryItem. Each subclass should have additional attributes representing the specific data associated with that subclass: author for Book; certificate and lead actor for DVD; and artist for CD. Also give each class a series of "getter" (accessor) methods to return each attribute, and give each class a suitable constructor. Use super appropriately.
  3. Give each class a getDescription() method of its own. This should return the full details as a string, e.g. a string containing film name, certificate and lead actor for the DVD. Use super appropriately.
  4. Test out your system with a main().

Inheritance in your chase game

Return to your chase game from topic 7. Create a new class, Creature, that contains all the attributes and methods common to both Hero and Monster. Make Hero and Monster inherit from Creature. Rewrite the game to use the inheritance hierarchy.