Home CPSC 220

Inheritance & Interfaces

 

Overview

We've seen that classes can be created which represent a group of data along with functions that work on that data.

Java also allows us to create relationships among classes. This can be useful in programming and is required to use certain Java libraries such as for doing GUIs or graphics.


 

Inheritance Relationships

There is another possible relationship between objects 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.

This is done in Java with the "extends" keyword:


class BaseClass {

}

class DerivedClass extends BaseClass {

}

Here, everything that is in BaseClass would be inherited by DerivedClass objects. We'd say that a DerivedClass object also is a BaseClass object.


 

Example

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 some only applies to employees:

If we make one class to represent all people, much of the information would be invalid. If we make two separate classes, we will have repetitive information.

The following program uses inheritance to solve this problem. The common information is contained in the base class, while unique information is in derived classes.


// base class for all people in system
class Person {
  private String name;
  private int idNumber;

  Person(String n, int i) {
    name = n;
    idNumber = i;
  }

  public void printPersonInfo() {
    System.out.printf("Name: %s\nID: %d\n", name, idNumber);
  }
}

// class for Students
class Student extends Person {
  private double gpa;
  private int credits;

  public Student(String n, int i, double g, int c) {
    // call the base class constructor
    super(n, i);
    gpa = g;
    credits = c;
  }

  public void printStudentInfo() {
    // call our printPersonInfo method
    printPersonInfo();

    // also print student-specific things
    System.out.printf("GPA: %f\nCredits: %d\n\n", gpa, credits);
  }
}

// class for Employees
class Employee extends Person {
  private int salary;
  private int years;

  public Employee(String n, int i, int s, int y) {
    super(n, i);
    salary = s;
    years = y;
  }

  public void printEmployeeInfo() {
    // call our printPersonInfo method
    printPersonInfo();

    // also print student-specific things
    System.out.printf("Salary: %d\nYears: %d\n\n", salary, years);
  }
}



public class Records {
  public static void main(String args[]) {
    Student student = new Student("Bob Smith", 23488347, 3.2, 40);
    student.printStudentInfo();

    Employee employee = new Employee("Alice Sanders", 485734, 56000, 4);
    employee.printEmployeeInfo();
  }
}

Notice that we must call the base class's constructor with the super keyword.


 

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".


 

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.

We can also replace methods in the base class by overriding them.

This is done as follows:


class Base {
  public void method() {
    // original
  }
}

class Derived extends Base {
  @Override
  public void method() {
    // new
  }
}

The "@Override" is actually optional, but is good practice.

The base class version of a method can be called from the derived class by prefixing it with "super." The following modified version of the example above uses overriding of the printInfo function:


// base class for all people in system
class Person {
  protected String name;
  protected int idNumber;

  Person(String n, int i) {
    name = n;
    idNumber = i;
  }

  public void printInfo() {
    System.out.printf("Name: %s\nID: %d\n", name, idNumber);
  }
}

// class for Students
class Student extends Person {
  private double gpa;
  private int credits;

  public Student(String n, int i, double g, int c) {
    // call the base class constructor
    super(n, i);
    gpa = g;
    credits = c;
  }

  @Override
  public void printInfo() {
    // call our printPersonInfo method
    super.printInfo();

    // also print student-specific things
    System.out.printf("GPA: %f\nCredits: %d\n\n", gpa, credits);
  }
}

// class for Employees
class Employee extends Person {
  private int salary;
  private int years;

  public Employee(String n, int i, int s, int y) {
    super(n, i);
    salary = s;
    years = y;
  }

  @Override
  public void printInfo() {
    // call our printPersonInfo method
    super.printInfo();

    // also print student-specific things
    System.out.printf("Salary: %d\nYears: %d\n\n", salary, years);
  }
}



public class Records2 {
  public static void main(String args[]) {
    Student student = new Student("Bob Smith", 23488347, 3.2, 40);
    student.printInfo();

    Employee employee = new Employee("Alice Sanders", 485734, 56000, 4);
    employee.printInfo();
  }
}

The benefit of this is that any person can now have the printInfo function called for them which will print the appropriate thing depending on which type of person they are.


 

Polymorphism

Polymorphism just means that different functions we call (like printInfo) can do very different things.

In the main function above, the student and employee objects each have the same interface for printing their info, but do so in different ways.

We can take this a step further by referring to them not as Students or Employees, but as People:


public class Records3 {
  public static void main(String args[]) {
    Person p1 = new Student("Bob Smith", 23488347, 3.2, 40);
    Person p2 = new Employee("Alice Sanders", 485734, 56000, 4);
    
    p1.printInfo();
    p2.printInfo();
  }
}

This demonstrates an important facet of Java: an object of a base class can refer to any derived object.

This demonstrates polymorphism as we have two objects that look the same (both Persons), yet work differently.

We can also create arrays of Person objects where each member of the array is a different sub-class of Person.


 

Interfaces

In Java, each class can only have one base class. However, there are times when this would be useful.

In order to get around this, Java has something called an interface. An interface is like a base class except:

An interface specifies a set of method declarations:


interface Animal {
  public void speak();
  public void eat();
}

If we want a class to inherit from this interface, we say that it implements it:


class Cat implements Animal {
  public void speak() {
    System.out.println("Meow.");
  }

  public void eat() {
    // eat cat food
  }
}

When implementing an interface, we must define all methods in the interface.

Like classes, interfaces must be defined in their own file if they are public.


 

The Comparable Interface

There are several interfaces that come with Java. Comparable is used to handle ordering and sorting of objects.

Suppose we wanted to sort our array of cards for some reason. We could try to call the "Arrays.sort" function:


Card cards[] = makeStandardPile();
Arrays.sort(cards);

However, this will give us the error "Card cannot be cast to java.lang.Comparable". This means that our Card is not "comparable" in a way that Java can understand. Luckily the only thing that the Comparable interface needs is a compareTo function. We can implement this interface for our cards:


// the Card class represents one standard playing card
class Card implements Comparable {

    // ...everything else...

    // the compareTo function we must implement
    public int compareTo(Object other) {
        // cast to a Card
        Card c = (Card) other;
    
        if (value < c.value) {
            return -1;
        } else if (value > c.value) {
            return 1;
        } else {
            return 0;
        }
    } 
}

Now our cards can be sorted by value. Notice we need to take the other card as an "Object" to do this.


 

Conclusion

Polymorphism is a powerful programming technique. It allows us to treat multiple different objects in a consistent manner.

Interfaces allow us to separate an interface from a particular implementation. This is what allows the Arrays.sort function to sort any kind of data without needing to know about it.

Inheritance and Interfaces are also needed for writing GUI and graphics code!

Copyright © 2024 Ian Finlayson | Licensed under a Attribution-NonCommercial 4.0 International License.