Home CPSC 240

Inheritance

 

Overview

So far we've seen a couple of different relationships between classes. A dependency is when one class simply uses another in one or more of its methods. The "has-a" relationship, along with aggregation and composition, is when one class contains an object of another class as an instance variable. Examples of this include a Deck class containing Card objects, or a Student class containing an instance of a Professor class as a reference to their advisor.

There is another important relationship between classes which models an is-a relationship. For example:

We can model this in our programs using inheritance. When a class inherits from another, it gets a copy of everything in that class.


 

Inheritance in UML

Suppose the school wanted to build a record system for storing information about people on campus. They want to store information on students and employees. Some of the information is the same for both:

Whereas some only applies to students:

And other information only applies to employees:

If we make one class to represent all people, much of the information would be invalid. For example, then all people would have a GPA field even though faculty and staff don't have GPAs. If we make two separate classes, we will have repetitive information. For instance, we'd need code to handle names in both the Student and Employee classes.

Here inheritance provides a good solution to this problem. We put the information that is shared in a base class, and then have two more specialized classes inherit from this. In UML, inheritance is shown with an arrow with an unshaded triangular arrowhead:

Inheritance in a UML diagram

This shows that Student and Employee both inherit from Person. This means that a Student object has not only a gpa and credits, but also a name and id number. Likewise it has the addCourse and getGPA methods, but also the getID and changeName methods.

There are lots of terms surrounding inheritance. We can say that the Person class here is the:

Likewise we can call the Student class a:


 

Inheritance in Java

Now let's look at how these classes might be implemented in Java. For the base class, Person, there is not really anything different we would need to do:


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

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

    public String getName() {
        return name;
    }

    public int getID() {
        return idNumber;
    }

    public void changeName(String newName) {
        this.name = newName;
    }
}

However when writing the Student class, we need to indicate that it is a subclass of Person. That's done with the extends keyword:


public class Student extends Person {
    private double gpa;
    private int credits;

    public Student(String name, int idNumber, double gpa, int credits) {
        // call the base class constructor
        super(name, idNumber);
        this.gpa = gpa;
        this.credits = credits;
    }

    public double getGpa() {
        return gpa;
    }

    public int getCredits() {
        return credits;
    }

    public void addCourse(int credits, String grade) {
        // ...
    }
}

By including the code extends Person, we make it so the Student class gets all of the stuff in the Person class: the instance variables, constructors and methods. We don't have to list them again, which would defeat some of the point of inheritance.

The other thing we need to talk about here is what's going on with the constructor. The Student constructor takes the information needed for it (gpa and credits) and also for its base class (name and idNumber). It can initialize its part of this itself, but it has to somehow relay the name and idNumber to the base class. In Java, the way this works is that the first line of the constructor has to be a call to super. When we call super, we are calling the constructor of our base class, and must pass in any parameters it needs.

The Employee class would then look very similar:


public class Employee extends Person {
    private int salary;
    private boolean fullTime;

    public Employee(String name, int idNumber, int salary, boolean fullTime) {
        super(name, idNumber);
        this.salary = salary;
        this.fullTime = fullTime;
    }

    public void processPayment() {
        // ...
    }

    public void giveRaise(double percent) {
        salary *= 1 + (percent / 100.0);
    }
}

Notice we do the same thing with the constructor calling super.

Now we can make objects of any of the three types of objects we've got. If we make a plain Person object then we can only call the Person methods of course. Person objects do not get anything from Student or Employee.


Person p = new Person("Bob Anderson", 1000);
System.out.println(p.getName());

If we make a Student object, we can call upon either the methods inherited from Person or the methods in the Student class:


Student s1 = new Student("Claire Carter", 1001, 3.8, 76);
System.out.println(s1.getName());
s1.addCourse(3, "A-");

 

Overriding Methods

Inheritance allows us to use another class as a starting point for a new one. We can then add new data and methods to it.

However, we can also choose to replace methods in the base class by overriding them. Overriding means that we re-implement the method in the derived class.

As an example, let's say we want to add a method called report to the Person class:


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

    // ... other methods ...

    public void report() {
        System.out.println("Name: " + name);
        System.out.println("ID: " + id);
    }
}

Now when we make the Student and Employee classes, they will inherit the report method. However, we might want to change it so that their report methods print out their more specific info too. To do that, we can just make a new report method in those classes. For example, the Student class might do it like this:


public class Student extends Person {
    private double gpa;
    private int credits;

    // ... other methods ...

    @Override
    public void report() {
        System.out.println("Name: " + getName());
        System.out.println("ID: " + getID());
        System.out.println("GPA: " + gpa);
        System.out.println("Credits: " + credits);
    }
}

Now, if we call report on a Student object, it will call the more specialized version that prints all the information. The "@Override" here is actually optional, but is good practice. The reason why is because if we accidentally mistype something about the method, the compiler will know that we meant to override a method instead of create a new one, and give us an error.

Here we can actually simplify this a little by having the Student's report method call the Person method to get it to print out the Person information itself. To call the base class version of a method, we can put "super." before it. Now it'll look like this:


    @Override
    public void report() {
        super.report();
        System.out.println("GPA: " + gpa);
        System.out.println("Credits: " + credits);
    }

 

protected Attributes

In the example above, the version of the Student report method which prints name and idNumber itself has to use the getter methods to access those fields. That's because they are private in the Person class. Private means no other classes can access those fields, which includes Student and Employee. So even though Student objects have names they cannot actually access them directly.

If we want to make it so instance variables can be accessed by subclasses, we can mark them protected instead of private. Protected data can be accessed only by the class itself and derived classes.

It's usually best to default to private though, and only use protected variables for cases when the subclasses really need access. That way the base class is better encapsulated.

The # sign is used for protected variables in UML.


 

The Object Class

No class we create is truly a base class in Java. A class that does not extend another one automatically extends the Object class. You can override the methods here including "equals" and "toString".


 

Benefits of Inheritance

The last of these, polymorphism, we will talk about next time.

Copyright © 2024 Ian Finlayson | Licensed under a Creative Commons BY-NC-SA 4.0 License.