Experiments in Java


Session O2: Inheritance

We use object-oriented languages for a number of reasons. Often, objects provide a natural mechanism for modeling the domain of the problem. For example, when we are writing a program to simulate a game, like chess, we might create an object for each component of the program (e.g., pieces, game board, user interface).

Reuse

In addition, object-oriented languages support reuse. After you design an object to use in one program, you will often find that you can reuse it in another program. For example, after building a Board class for chess, you could reuse that object in a checkers game. This is the simplest form of reuse.

But what if we want to not only reuse a class, but also extend that class to add new capabilities? For example, suppose we wanted to have a class like Point, but one that had a toString() method that converted points to strings. What could we do?

We could obtain the source code for Point (i.e., the file Point.java) and modify that source code. An advantage of this solution is that we need not change any of the programs that already use the Point class. A disadvantage is that there will soon be many different versions of the Point class. For example, while you may decide to add a toString method, someone else might decide to add a distanceFrom(Point other) method and someone else might decide to add both. In addition, the source code for a class is not always available.

We could build a new class, NewPoint, which contains the same methods as Point, as well as the new methods we want to add. Rather than reimplementing all of Point's methods, we could include a field that contains a base point and simply call the appropriate method based on that field. For example, we might have the following attribute declaration

  /**
   * The point that this point is based on.
   */
  protected Point base;

When creating a new NewPoint, we just need to create a new Point, as in

  /**
   * Build the point (x,y).
   */
  public NewPoint(double x, double y) {
    base = new Point(x,y);
  } // NewPoint(double, double)

To get the x coordinate of a NewPoint we might use

  /**
   * Get the X coordinate of a point.
   */
  public double getX() {
    return base.getX();
  } // getX()

Similarly, to move a NewPoint right, we might use

  /**
   * Shift the point right by a particular amount.
   */
  public void shiftRight(double amt) {
    base.shiftRight(amt);
  } // shiftRight(double)

Look at NewPoint.java for additional details.

There are a number of disadvantages to this strategy, too. In particular, there is a lot of busy work writing a number of methods with just one call to the base object. In addition, you cannot use a NewPoint where you had previously used a Point.

You will discover some of these disadvantages in Experiment O2.1.

Inheritance

Because experience shows that many times programmers need to build extended versions of existing classes, and because of the problems described in the previous section, most object-oriented languages provide a more sophisticated reuse mechanism called inheritance. What is inheritance? Put most simply, it is a way to add functionality to a class without changing the underlying class. A class that inherits from another class automatically includes essentially all of the methods and fields of the class that it inherits from. For example, if we based an ExtendedPoint class on our Point class, then ExtendedPoint would automatically include getX, getY, shiftLeft, and all of the other methods provided by the Point class without requiring any additional code.

A class that inherits from another class is also said to extend that class. The original class is called the superclass and the class that inherits from that class is called the subclass.

Inheritance is so important to object-oriented programming that some computer scientists do not consider a language object-oriented unless it supports inheritance. However, as we will see, there are a number of alternative notions of inheritance.

How do you build a subclass in Java? Using the extends in the class declaration. For example

public class ExtendedPoint
  extends Point {
  ...
} // ExtendedPoint

What needs to go in a subclass? Officially, nothing, in which case the subclass behaves identically to the superclass. However, there is little purpose to extending a class if you are not going to add some functionality through additional or modified methods. In addition, you will almost always need to add new constructors to a subclass because a subclass does not inherit the constructors of the superclass.

For example, we might write

  /**
   * Build the point (x,y).
   */
  public ExtendedPoint(double x, double y) {
    setValue(x,y);
  } // ExtendedPoint(double, double)

This does lead to a question. What should we do if we want to use one of the constructors defined in the superclass? Fortunately, Java provides a solution. If you want to use one of the superclass's constructors, you use

    super(parameters);

For example, if the superclass is Point and the two parameters are of type double, then the Point(double,double) constructor will be used.

Taking advantage of this feature, we might instead define the constructor for ExtendedPoint as

  /**
   * Build the point (x,y).
   */
  public ExtendedPoint(double x, double y) {
    super(x,y);
  } // ExtendedPoint(double, double)

You will experiment with a simple extension of the Point class in Experiment O2.2. In Experiment O2.3 you will add additional functionality to that new class.

Overriding methods

Most frequently, we extend classes to add additional functionality. For example, we might build an ExtendedPoint to extend Point with a method like toString, which provides a printable version of the point, or distanceFrom ,which computes the distance between a point and another point.

However, sometimes we want to modify the functionality of an existing method. For example, Point includes a distanceFromOrigin method that gives the straight-line distance of a point from the origin. What should we do if we'd like to have points that use taxicab distance in which distance is measured along horizontals and verticals, but not diagonals? In object-oriented terminology, we override the original method. We do that by creating a new method with the same name as the overridden method. For example,


/**
 * A point on the plane that uses taxicab distance rather than
 * standard distance.  Intended as a simple example of inheritance 
 * and overriding in Java.
 *
 * @version 1.1 of October 1999
 * @author Samuel A. Rebelsky
 */
public class TaxicabPoint extends Point {

  // +--------------+--------------------------------------------
  // | Constructors |
  // +--------------+

  /**
   * Build the point (x,y).
   */
  public TaxicabPoint(double x, double y) {
    super(x,y);
  } // TaxicabPoint(double, double)

  /**
   * Build the point (0,0).
   */
  public TaxicabPoint() {
    super(0,0);
  } // TaxicabPoint()

  
  // +---------+-------------------------------------------------
  // | Methods |
  // +---------+

  /**
   * Compute the distance of a point from the origin.  Overrides 
   * corresponding method from the Point class.
   */
  public double distanceFromOrigin() {
    return this.getX() + this.getY();
  } // distanceFromOrigin

} // class TaxicabPoint


In Experiment O2.4 you will conduct a short experiment with TaxicabPoints. In Experiment O2.5 you will override the toString method we've added to ExtendedPoint.

From rectangles to squares

Let us turn to a more complicated form of inheritance. Suppose we were extending the Rectangle class to develop a Square class. Is this a reasonable thing to do? Some would argue yes, since squares are particular kinds of rectangles. Others would argue no, since squares provide a somewhat different semantics than do rectangles (e.g., if you change the width of a rectangle, you do not affect its height; however, if you change the width of a square, you must also change its height). We will assume that this is a reasonable extension and consider how to build the Square class.

What are the potential difficulties of such an extension? In general, they relate to ensuring that the square remains a square. For example, when we set the width to a particular value, we also need to set the height to the same value (and vice versa).

In Experiment O2.6 you will build a Square class from a Rectangle class.

Multiple inheritance

At times, you may find that you want to create a class that inherits methods from multiple classes. For example, if you've extended Point to PrintablePoint (adding a toString method) and also extended Point to ComparablePoint (adding equals and lessThan methods), you might consider writing a PrintableAndComparablePoint by extending both PrintablePoint and ComparablePoint. This is called multiple inheritance.

Unfortunately (or fortunately, depending on your perspective), Java does not support multiple inheritance. Why not? Primarily, because it can be complicated to understand. For example, if class AB extends both A and B and both A and B provide a toString method, then which does AB use? In addition, it turns out that multiple inheritance is difficult to implement efficiently.

As an alternative to multiple inheritance, Java provides interfaces, which are discussed in a subsequent laboratory session. It turns out that most of the multiple inheritance that is done in practice can be handled more elegantly, more appropriately, and more efficiently by interfaces.


Copyright (c) 1998 Samuel A. Rebelsky. All rights reserved.

Source text last modified Tue Oct 26 13:20:52 1999.

This page generated on Tue Oct 26 15:38:07 1999 by Siteweaver.

Contact our webmaster at rebelsky@math.grin.edu