INTRODUCTION TO INHERITANCE


Contents


INHERITANCE

Motivation and Simple Example

When you have a class C, and you want another class, D, that does everything that C does and more: use inheritance! Here's a simple example:

In general, inheritance is used to implement an "is-a" relationship, not a "has-a" relationship. For example, a racehorse is a horse, as is a workhorse, so a reasonable inheritance hierarchy is:

          Horse
	  /   \
 RaceHorse   WorkHorse
(In this hierarchy, Horse is the base class; it is the superclass of both RaceHorse and WorkHorse, and both RaceHorse and WorkHorse are subclasses of Horse.) On the other hand, since a horse has a head, a body, and a tail, and a head has a nose and a mouth, the following would not be a good inheritance hierarchy:
          Horse
	 /  |  \
     Head Body Tail  
     /  \
  Nose  Mouth
It may make sense to define classes Horse, Head, Body, Tail, Nose, and Mouth, but the way they should be related is that the Head class has two fields of type Nose and Mouth, respectively, and the Horse class has three fields, of type Head, Body, and Tail.

The main benefit of inheritance is code reuse:

Note: You should probably not use inheritance if you don't want most of the existing class's methods unchanged, or if functions with parameters of that type will not still work correctly when passed actual parameters of the new subclass type. Here's an example:

Note that in general you should think about the whole class hierarchy that you will need before defining any classes (i.e., design your base class so that it can be extended to create the subclasses you want). This is not always easy, but it should pay off in the long run.

Inheriting Fields and Methods

Fields

Every field of the superclass is inherited. For example:

Every RaceHorse has four fields: 3 inherited fields and 1 new one.

Notes:

  1. An inherited field can be hidden (by defining a new field with the same name). For example, if we add the line: to the RaceHorse class, every RaceHorse will have five fields, one of which (int myAge) is hidden. However, it is a bad idea to hide fields because it can lead to confusion and incorrect code.

  2. Private fields are inherited, but cannot be accessed by the methods of the subclass. For example, suppose we decide that the RaceHorse's WinRace method should increase that horse's value is well as incrementing the number of races won. If we add the following to the RaceHorse class: we get a compile-time error, because the myValue field is a private field of the Horse class (so cannot be accessed in a RaceHorse method). The same thing happens if the superclass field has package access and the subclass is in a different package.

    To address this problem, fields can be defined to be protected, which means that subclass methods can access them, or the superclass can provide public or protected functions to access and modify its private fields. For example, if the Horse class includes:

    then the RaceHorse class can implement the WinRace method like this:

    Methods

    Each superclass method (except its constructors) can be either

    inherited: If no method with the same name is (re)defined in the subclass, then the subclass has that method with the same implementation as in the superclass.

    overloaded: If the subclass defines a method with the same name, but with a different number of arguments or different argument types, then the subclass has two methods with that name: the old one defined by the superclass, and the new one it defined.

    overridden: If the subclass defines a method with the same name, and the same number and types of arguments, then the subclass has only one method with that name: the new one it defined.

    Example:

    class Horse {
        ...
        public double GetValue() {
            return myValue;
        }
        
        public void Birthday() {
            // increment horse's age, recompute its value
        }
    
        public void Sell( String newOwner ) {
            // change the myOwner field
        }
    }
    
    class RaceHorse extends Horse {
        ...
    
        public void Birthday( double d ) {
            // increment horse's age, set value to given amount
        }
        
        public void Sell( String newOwner ) {
            // change the myOwner field and set the myNumRacesWon field to 0
        }
    }
    
    Now every RaceHorse has:

    Constructors

    A subclass's constructors always call a superclass constructor, either explicitly or implicitly. If there is no explicit call (and no call to another of the subclass constructors), then the no-argument version of the superclass constructor is called before executing any statements.

    Example:

    When the code "r = new RaceHorse()" is executed, the following happens:
    1. RaceHorse's no-arg constructor is called
    2. Horse's no-arg constructor is called
    3. myValue is set to 0
    4. myNumRacesWon is set to 0

    Explicit calls to a superclass constructor can be made using super:

    And here is an example to show both a call to a superclass constructor, and also how one subclass constructor can call another subclass constructor:

    Notes:

    1. If you want to call a superclass constructor or another of the subclass constructors, that call must be the first statement.

    2. If you write no constructors for a class, you will get a default no-argument constructor that just calls the superclass's no-argument constructor.

    TEST YOURSELF #1

    Assume the following class has been defined:

    Define a class ColorPoint that:

    solution


    Extending the Object Class

    Recall that every class extends Object. So you might wonder which methods of Object you should consider overriding when you define a new class. There are four methods that often should be overridden:

    1. toString
    2. equals
    3. hashCode
    4. clone

    public String toString(): Returns a String representation of the object. It is used, for example, by System.out.print to print an object. The default version of toString is not very useful, so you should override this method whenever you want to provide a String representation of your class objects.

    public boolean equals(Object ob): Returns true iff the object (pointed to by "this") and ob are the same. The default version uses pointer equality; i.e., it returns true only if "this" and "ob" contain the same address. You may want to override this method to provide a more liberal notion of equality. For example, the String class overrides equals so that it returns true for two Strings that contain the same sequence of characters.

    public int hashCode(): Returns an integer for this object suitable for use as a hash code (e.g., for use with the Hashtable class defined in javil.util). This method should be overridden whenever the equals method is, so that hashCode returns the same value for two "equal" objects.

    protected Object clone(): Returns a copy of this object (note that no constructor is called for the new object). The default version just copies the values of all fields (i.e., a "shallow" copy). That is probably not what you want when your class has fields that contain pointers (i.e., arrays or classes). So in that case you should override the clone method to do a deep copy -- clone all pointer fields.

    Cloning

    To permit your object to be cloned you must declare that your object implements the Cloneable interface. (See the notes on INTERFACES.) For example:

    If you forget to do this, an attempt to clone will cause the exception CloneNotSupportedException to be thrown.

    Given the declaration above, List methods will be able to clone List objects; however, users of the List class will not be able to call clone because it is a protected method of the Object class. Also, the (default) clone method will just copy the value of items, creating a new List object whose items field points to the same array (not good!). For example:

    List L1 = new List();
    List L2 = L1.clone()
    
                 +--+    +--------------+
        List L1: | -|--> |        +--+  |      +-----------+
                 +--+    | items: | -|--|----> |  |  |  |  |
    	             |        +--+  |  +-> +-----------+
    		     | ...          |  |
    		     +--------------+  |
                                           |
                 +--+    +--------------+  |
        List L2: | -|--> |        +--+  |  |        L2 points to a newly allocated
                 +--+    | items: | -|--|--+        List object, but its items
    	             |        +--+  |           field points to the same
    		     | ...          |           array as L1's items field!
    		     +--------------+
    
    To avoid these two problems, you should redefine the clone method for class List as follows: Notes on this example code:

    Summary of cloning: To permit your class objects to be cloned, you must:

    1. Make your class implement Cloneable.
    2. Redefine the clone method:
      • make it public
      • start with the call "super.clone()" inside a try block
      • for every field of your object that is really a pointer, clone that field (in the same try block).

    TEST YOURSELF #2

    Consider the following program:

    Question 1: What is printed when this program is run?

    Question 2(a): Write a new version of the program in which main uses cloning to copy from p1 to p2 (instead of assignment). Be sure to include all appropriate changes to the Point class.

    Question 2(b): What is printed when your new version of the program is run?

    Question 3: Now suppose that the x,y coordinates of a Point are stored in an array, rather than in two int fields; i.e., suppose we replace:

    with: and that we change the SetPoint method to: and that we change the toString method appropriately. Write a new version of the clone method so that there is no aliasing when a Point is cloned.

    solution


    Summary

    Solutions to Self-Study Questions

    Test Yourself #1

      class ColorPoint extends Point {
        protected String color;
      
        // no-arg constructor
        public ColorPoint() {
          super();  // not needed, but makes clear that superclass constructor
          	      // is called first
          color = "red";
        }
      
        // 2-arg constructor
        public ColorPoint(int a, int b) {
          this(a, b, "red");
        }
      
        // could also use:
        //
        //  public ColorPoint(int a, int b) {
        //    super(a, b);
        //    color = "red";
        //  }
        //
        //  public ColorPoint(int a, int b) {
        //    x = a;
        //    y = b;
        //    color = "red";
        //  }
      
        // 3-arg constructor
        public ColorPoint(int a, int b, String c) {
          super(a, b);
          color = c;
        }
      
        // could also use:
        //
        //  public ColorPoint(int a, int b, String c) {
        //    x = a;
        //    y = b;
        //    color = c;
        //  }
      }
      

    Test Yourself #2

      Question 1: What is printed when this program is run? 
      
      	(20,30)
      
      Question 2(a): Write a new version of the program in which main uses
      cloning to copy from p1 to p2 (instead of assignment). Be sure to
      include all appropriate changes to the Point class.
      
      class Point implements Cloneable {
        // fields and constructors as before...
      
        public Object clone() {
          try {
            Point tmp = (Point) super.clone();
            return tmp;
          } catch (CloneNotSupportedException ex) {
            throw new InternalError();
          }
        }
        public static void main(String[] args) {
          ...
          Point p2 = (Point) p1.clone();
          ...
        }
      }
      
      Question 2(b): What is printed when the new version of this program is run?
      
               (10, 20)
      
      Question 3: Write a new version of the clone method so that there is
      no aliasing when a Point is cloned.
      
        public Object clone() {
          try {
            Point tmp = (Point) super.clone();
            tmp.coords = (int[]) coords.clone();
            return tmp;
          } catch (CloneNotSupportedException ex) {
            throw new InternalError();
          }
        }