Introduction to inheritance in Java

Inheritance is a mechanism that allows us to define a class as an extension or customized version of another class. An inheriting class is called a subclass and an inherited class a superclass. Objects of a subclass can be seen as consisting of (sub)objects of the inherited superclasses plus some possible additional or substituting (“overriding”) members defined by the subclass.

In order to keep things simple, Java allows only single inheritance: a class may directly inherit at most one class. A class can indirectly inherit multiple classes, but the inheritance hierarchy (like a “family tree”) will be linear, e.g. “class Teacher inherits class Employee that inherits class Person. Here class Teacher would inherit Employee directly and Person indirectly via Employee. Teacher objects would have the properties of both Employee and Person.

A Java class may inherit another class by adding the keyword extends and the name of the inherited superclass after the class name. If you already know C++ inheritance, it is worth noting that Java does not use inheritance accessibility modifiers: Java inheritance works in similar fashion as public inheritance in C++. A subclass can refer to all non-private members of its superclasses. A subclass has no access to their private members and thus can manipulate those only within the possibilities offered by non-private functions of the superclasses.

Let us consider an example where the previous three classes would have roughly the following forms:

Definitions of classes Person, Employee and Teacher.

A sketch of classes Person, Employee and Teacher.

The following figure shows how their objects would be represented in Java.

The structures of Person, Employee and Teacher objects.

A sketch of he structures of Person, Employee and Teacher objects.

Note how a Teacher object first contains the non-static member variables of Person, then of Employee and only finally of Teacher itself. In similar manner an Employee object contains first the non-static member variables of Person and only then of Employee. This corresponds to how each class extends its superclass. Some of the member variables are shown in light color. This depicts the fact that they are private members and thus the subclass cannot access them directly even though they are contained inside the same object. The figure omits e.g. the technical detail that each Java class object also has a reference to the so-called virtual table of its class. Virtual tables contain information about member functions.

The next figure lists the constructors and public member functions of Person, Employee and Teacher. The figure illustrates the fact that constructors are not inherited, but non-private member functions are. Here inheriting a function does not mean that the inheriting function would hold its own separate copy of the function; it simply means that non-private functions of superclasses can be called via subclass objects.

Constructors and public member functions of the Person, Employee and Teacher classes.

Constructors and public member functions of the Person, Employee and Teacher classes.

It is important no note that subclass constructors must ensure that also the superclasses are initialized properly. To this end a subclass constructor must pass any required parameters to a superclass constructor. The preceding example already hinted at this in how the subclass constructors received more parameters than needed to initialize their “own part”: they also received parameters required in order to initialize superclasses. Such parameters can be passed to a superclass constructor via the keyword super, and this must be done as the first step of a constructor.

The example code below shows complete Java implementations for Person, Employee and Teacher. The classes should be placed into separate files.

public class Person {
  private String name;
  private String personId;

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

  public String getName() {
    return name;
  }

  public String getPersonId() {
    return personId;
  }
}

public class Employee extends Person {  // Inherit Person: add "extends Person" to the end.
  private String title;
  private float salary;

  public Employee(String name, String personId, String title, float salary) {
    super(name, personId);  // First initialize the superclass Person.
    this.title = title;     // Then perform own initializaton steps.
    this.salary = salary;
  }

  public String getTitle() {
    return title;
  }

  public float getSalary() {
    return salary;
  }
}

import java.util.List;  // Teacher has a List member.

public class Teacher extends Employee { // Inherit Employee: add "extends Employee" to the end.
  private List<String> classes;

  public Teacher(String name, String personId, String title, float salary,
          List<String> classes) {
    super(name, personId, title, salary); // First initialize the superclass Employee.
    this.classes = classes;
  }

  public List<String> getClasses() {
    return classes;
  }
}

A subclass can define a similar (same name and compatible parameter and return types) member function as its superclass. This will result in overriding (ie. replacing) the inherited function.

Sub- and superclasses are “compatible” in a bottom-up direction in the inheritance hierarchy. A subclass object can be used as if it was an object of its superclass. It is e.g. legal to refer to a subclass object with a superclass type variable. This reflects how a subclass in principle inherits the properties of its superclasses. Inheritance is often called an “is a” relationship: subclass objects are (also) objects of their superclasses. As far as an object oriented programming language is concerned, a subclass object can be used in all contexts where a superclass object can. In practice this does require that a subclass has not defined some overriding features that lead to unexpected results with respect to a superclass. This should never happen: it is very bad style to break the behaviour expected from a superclass.

Java has a special superclass Object that is inherited by all reference types (including arrays). All objects therefore have e.g. the following public member functions defined by Object:

  • toString(): return a String representation of the object.

    • The default implementation inherited from Object only prints out vague information about the identity of the object.

    • When you print an object x e.g. as System.out.println(x), the object x is automatically converted into a string by calling x.toString.

  • equals(Object b): returns true or false depending on whether this object is similar to b.

    • The default implementation inherited from Object only compares object identity and not “real” similarity.

  • hashCode(): returns a hash code for this object (as an int).

    • The default implementation inherited from Object typically bases the hash code on the identity (e.g. memory address) and not “the value” of the object.

Since the default implementations inherited from Object usually rely only on the identity and not the value represented by an object, most other classes should define their own overriding versions of these functions.

Below is an example class ObjectList that inherits ArrayList<Object> and whose only self-defined function is toString. Many classes override especially this function due to how it simplifies printing objects. The example also notes that Java’s list containers have a common supertype List. E.g. ArrayList<Object>, ObjectList and LinkedList<Object> objects can therefore be handled ina uniform manner using List<Object> references. It should be noted that a member reference must be legal with respect to the type of the corresponding reference variable. Therefore e.g. the example code would not be able to call the ArrayList member function trimToSize via a List<Object> reference: List does not have such a member function.

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

public class ObjectList extends ArrayList<Object> {
  // An overriding implementation of toString. This also shows an example of Java's
  // @Override annotation. Java allows (and it is recommended although not mandatory)
  // to mark an overriding function by placing the annotation "@Override" before it. This
  // directs the Java compiler to verify that the function in fact overrides a similar
  // superclass function (this helps to avoid typos and such).
  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder(this.size() + " values:\n");
    for(int i = 0; i < this.size(); i++) {
      sb.append("  ").append(Integer.toString(i)).append(": ")
              .append(this.get(i).toString());
    }
    return sb.toString();
  }

  public static void main(String[] args) {
    // Create an ObjectList and add values "one", 2, 3.0 ja "four" into it.
    // This in effect uses the add function ineherited from ArrayList.
    ObjectList myList = new ObjectList();
    myList.add("one");
    myList.add(2);
    myList.add(3.0);
    myList.add("four");

    // Create and print also an ArrayList<Object> and a LinkedList<Object> objecdt that
    // are initialized with contents of myList. This is doable because Java containers
    // can often be initialized by another container. Also ObjectList is a Java container
    // because it is a subclass of the Java container class ArrayList.
    ArrayList<Object> arrList = new ArrayList<>(myList);
    LinkedList<Object> linkedList = new LinkedList<>(myList);
    System.out.format("myList:%n%s%n%n", myList);
    System.out.format("arrList:%n%s%n%n", arrList);
    System.out.format("linkedList:%n%s%n%n", linkedList);

    // An ObjectList object can be handled with a superclass reference type ArraList<ObjectA>.
    ArrayList<Object> myList2 = myList;

    // All Java list containers can also be handled using a supertype reference type List. Below
    // each of the preceding lists are printed with the function printList that takes an object of
    // type List<Object> as a parameter. Also ObjectList is acceptable as such due to how
    // ObjectList inherits being a List via the superclass ArrayList.
    printList("myList", myList);
    printList("arrList", arrList);
    printList("linkedList", linkedList);
  }

  // Prints out the items of a list of type List<Object>.
  // The output contains the list name given by the parameter "title".
  private static void printList(String title, List<Object> list) {
    System.out.format("printList(%s):%n", title);
    for(int i = 0; i < list.size(); i++) {
      System.out.format(" %s[%d]: %s", title, i, list.get(i));
    }
    System.out.format("%n%n");
  }
}

Executing the example outputs:

myList:
4 values:
  0: one  1: 2  2: 3.0  3: four

arrList:
[one, 2, 3.0, four]

linkedList:
[one, 2, 3.0, four]

printList(myList):
myList[0]: one myList[1]: 2 myList[2]: 3.0 myList[3]: four

printList(arrList):
arrList[0]: one arrList[1]: 2 arrList[2]: 3.0 arrList[3]: four

printList(linkedList):
linkedList[0]: one linkedList[1]: 2 linkedList[2]: 3.0 linkedList[3]: four

Note how the function printList could call list.size() and list.get(i). The type List<Object> has such public functions and therefore also its subclasses, such as ArrayList<Object> and ObjectList, must have similar puvlic functions. Our ObjectList did not implement them itself, but it inherited them from the superclass ArrayList<Object>.

A seconf, and very important, note about the function calls list.size() and list.get(i) is that they are directed during runtime to the member functions of the list object’s actual class: e.g. although list is a List<Object> reference, the call list.get(i) was directed to the function of LinkedList<Object> insted of the type List<Object> when list referred to the object linkedList. This behaviour illustrates the fact that all Java member functions are inherently “virtual”, that is, a member function reference via an object is resolved during runtime so that it corresponds to the actual type of the object. The “non-virtual” alternative, which is e.g. the default behaviour in C++, would be to resolve member function calls already during code compilation based on the reference type (in which case e.g the calls list.size() and list.get(i) would have been directed to member functions size and get of the type List<Object>).

It is often said that one of the main motivations for using inheritance is code reuse: one does not need to implement everything from scratch if useful existing functionality can be inherited from superclasses. But the most important reason to use inheritance could be said to be the possibility to handle various object types in a uniform manner, via a common supertype. E.g. the preceding function printList can handle all types of objects whose supertype is List<Object>. Inheritance made it possible to do this with one function instead of having to e.g. implement three separate functions printList(ArrayList<ObjectList>), printList(ArrayList<Object>) and printList(LinkedList<Object>).

If you read the text carefully, you might have noticed that we never referred to List as a class: it was always referred to in a more general manner as a “type” List. This is because List is not a class: it is an interface. We will discuss interfaces later.

A Java class may inherit another class by adding

The subclass can

Java has a special superclass Object that is inherited by all reference types (including arrays).

Posting submission...