CMUQ 15-121 Inheritance



1. Introduction

Inheritance is a convenient method of sharing code and data between related classes. When one class inherits from another, we say there is an is-a relationship between them. When one class inherits from another, it receives copies of relevant instance variables and methods without you needing to rewrite the code for them.

In these notes we’ll go over the basics of inheritance by using a simple example.

2. Doing Things the Hard Way

Imagine that you are working on a Java program and you need a class to store some basic information about a person, such as their name and age. You would likely come up with something like this:

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void birthday() {
        this.age++;
    }

    public String toString() {
        return this.name + " (Age: " + this.age + ")";
    }

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}

It is a simple class with a basic constructor, a birthday method to change the age, a toString, and a few getter methods for the name and age.

Now, later on, while extending the program you also need a class to store information about a student. In your mind, a student is-a Person, so they will have a lot in common. So much, in fact, that you find yourself copying a lot of code from Person into your new Student class:

public class Student {
    private String name;
    private int age;
    private String major;

    public Student(String name, int age, String major) {
        this.name = name;
        this.age = age;
        this.major = major;
    }

    public void birthday() {
        this.age++;
    }

    public String toString() {
        return this.name + " (Age: " + this.age + ")" + " (Major: " + this.major + ")";
    }

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public String getMajor() {
        return this.major;
    }
}

The code is so similar, in fact, that this is basically the Student class with just a little bit of extra code added to handle the major.

Situations like this come up a lot in development, and there is a much better way to handle it than simple duplicating code.

3. Doing Things the Better Way

Remember, we previously noticed that a student is-a person. Given this, we can actually tell Java this and have the Student class automatically receive data and code from the Person class. To do this we use the extends keyword. Let’s take a look:

public class Student extends Person {
    private String major;

    public Student(String name, int age, String major) {
        super(name, age);
        this.major = major;
    }

    public String toString() {
        return super.toString() + " (Major: " + this.major + ")";
    }

    public String getMajor() {
        return this.major;
    }
}

This code is much smaller, but is functionally identical to the previous Student class we wrote. Here’s another version of the same thing, but with comments to talk you through it:

// Notice how we use "extends" to tell Java that Student should inherit from Person.
public class Student extends Person {

    /*
     * Here we declare a new instance variable, major, to store the major. We don't
     * need to redeclare name and age, Student inherits those for free.
     */
    private String major;

    // We don't, however, inherit constructors. So we need to provide one.
    public Student(String name, int age, String major) {
        /*
         * This next line is interesting. We can actually call the constructor from
         * Person using super(), so we don't need to repeat that code.
         */
        super(name, age);

        /*
         * But, that parent constructor doesn't initialize major for us, since that only
         * exists here in Student. So we do that here.
         */
        this.major = major;
    }

    /*
     * We provide a new version of toString, because we want ours to be different
     * than the one in Person. Notice, however, that this toString() does call the
     * toString() from Person and use its result.
     */
    public String toString() {
        return super.toString() + " (Major: " + this.major + ")";
    }

    /*
     * We need our own getter for major because Person doesn't have one.
     */
    public String getMajor() {
        return this.major;
    }
}

4. Terminology and Limitations

Let’s take a look at some terminology around inheritance so we have common vocabulary to talk about it:

There also some limitations when using inheritance:

5. Superclass and Subclass Compatibility

In a program, subclasses can be stored in any place the superclass can. A variable of type T can hold an object of type T or any subclass of T. This means, for example, that a Person variable can have a Student object assigned to it. When this happens, however, you can’t access any methods or variables that only exist in the subclass.

For example:

Person p = new Student("John Smith", 19, "InfoSys");
System.out.println(p.getName());
System.out.println(p);

Will print out:

John Smith
John Smith (Age: 19) (Major: InfoSys)

However, you can’t do this:

Person p = new Student("John Smith", 19, "InfoSys");
System.out.println(p.getMajor());

In this case, the call to getMajor() can’t happen because you’ve declared p to be a Person, and the Person class doesn’t have a getMajor() method.

6. Superclasses and Subclasses in Lists

You can store superclasses and their subclasses into the same arrays or lists. When you pull the items out, however, they will all have the same type as the superclass. In order to access subclasses as subclasses, you’ll need to cast. Here’s an example illustrating all of this:

ArrayList<Person> bigList = new ArrayList<Person>();
bigList.add(new Person("Bob Jones", 45));
bigList.add(new Student("John Smith", 19, "InfoSys"));
bigList.add(new Person("Bridget Johnson", 22));
bigList.add(new Student("Muna Al-Mannai", 21, "CompSci"));

for (Person p : bigList) {
    System.out.println(p);
}

for (Person p : bigList) {
    // Get the name of the person
    String name = p.getName();

    // Check if p is actually a Student
    if (p instanceof Student) {
        /*
         * Since they are, we can cast the Person to Student. This allows use to call
         * getMajor() on it.
         */
        Student s = (Student) p;
        String major = s.getMajor();
        System.out.println(name + " is a Student with major " + major);
    } else {
        System.out.println(name + "is a Person");
    }
}