Experiments in Java


Session O2: Inheritance

In this laboratory session, you will learn about inheritance, one of the key characteristics of object-oriented languages. The goals of this laboratory session are to

Your instructor will tell you which of the experiments you are to perform.

Prerequisite skills:

Note: It is possible to do this lab directly after J3, although some post-laboratory problems also require J4 (Boolean Expressions and Conditionals).

Required files:


Discussion

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.


Experiments

Name: ________________
ID:_______________

Experiment O2.1: Building a new point class

Required files:

Step 1. Make copies of Point.java, PointFun.java, and NewPoint.java. Compile all three and execute PointFun. Record your results.


 
 
 
 
 
 

Step 2. Replace the line in PointFun that reads

    Point pt = new Point(2,3);

with

    Point pt = new Point();

Recompile PointFun and execute PointFun. Record your results.


 
 
 
 
 
 

Step 3. Make a copy of PointFun.java called NewPointFun.java (you'll also have to change PointFun to NewPointFun within the code). Update NewPointFun to use a NewPoint wherever it used a Point. Summarize your changes here.


 
 
 
 
 
 

Step 4. Compile and execute NewPointFun. Record your results. Are they the same as in step 2?


 
 
 
 
 
 

Step 5. Update the zero-parameter constructor for Point to read

  /**
   * Build the point (1,1).
   */
  public Point() {
    this.xcoord = 1;
    this.ycoord = 1;
  } // Point()

Do not change NewPoint or NewPointFun. Recompile Point and run NewPointFun. Record and explain the output.


 
 
 
 
 
 

Step 6. As you may have observed, the zero-parameter constructor for NewPoint no longer creates the point (0,0). Update that constructor so that it always creates that point. Summarize your changes here.


 
 
 

Step 7. Record any general observations you have about the process of creating a variant of the Point class in this experiment.


 
 
 
 
 
 

Experiment O2.2: A simple extension of the Point class

Required files:

Step 1. Make fresh copies of Point.java and PointFun.java. Compile the two programs. Execute PointFun. Record the results.


 
 
 
 
 
 

Step 2. Build a new ExtendedPoint class containing only the following lines along with an appropriate introductory comment.

public class ExtendedPoint
  extends Point {
} // class ExtendedPoint

Compile the new class and record any error messages you receive. After doing so, read the notes on this step.


 
 
 

Step 3. Modify PointFun to use an ExtendedPoint in place of each Point. Summarize your changes.


 
 
 
 
 
 

Step 4. Attempt to compile the new class and record any error messages you receive. What do these error messages suggest? After writing your answer, read the notes on this step.


 
 
 
 
 
 

Step 5. Change the line in PointFun's main method that creates the point from

    ExtendedPoint pt = new ExtendedPoint(2,3);

to

    ExtendedPoint pt = new ExtendedPoint();

Recompile and execute PointFun. Record the output. What does this suggest?


 
 
 
 
 
 

Step 6. Add the following constructor to ExtendedPoint.

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

Recompile both PointFun and ExtendedPoint. If you are successful, record and explain the results. If you are not successful, record and explain the error messages.


 
 
 
 
 
 

Step 7. Add the following constructor to ExtendedPoint.

  /**
   * Build the point (1,1).
   */
  public ExtendedPoint() {
    setValue(1,1);
  } // ExtendedPoint()

Recompile both PointFun and ExtendedPoint. If you are successful, record and explain the results. If you are not successful, record and explain the error messages.


 
 
 
 
 
 

Step 8. Change the line in PointFun's main method that creates the point from

    ExtendedPoint pt = new ExtendedPoint();

to

    ExtendedPoint pt = new ExtendedPoint(4,1);

Recompile and execute PointFun. Record the output. What does this suggest?


 
 
 
 
 
 

Step 9. Replace the appropriate constructor in ExtendedPoint with

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

Recompile both PointFun and ExtendedPoint. Run PointFun. If you are successful, record and explain the results. If you are not successful, record and explain the error messages.


 
 
 
 
 
 

Experiment O2.3: Adding functionality to ExtendedPoints

Required files:

Obviously, we want subclasses to do more than their superclasses do, otherwise there is no purpose to having subclasses. In particular, we often want to add new methods to subclasses. In this experiment, we will add a toString method that will convert points to printable strings.

Step 1. Make copies of Point.java, PointFun.java, and ExtendedPoint.java. If you have not already done so, update PointFun.java to use ExtendedPoints rather than Points. Compile and execute PointFun and record the results.


 
 
 
 
 
 

Step 2. Add the following toString method to ExtendedPoint.

  /**
   * Convert the point to a string for ease of printing.
   */
  public String toString() {
    return "x=" + getX() + ", y=" + getY();
  } // toString()

Recompile ExtendedPoint and PointFun. Execute PointFun. Record your results. Are they what you expected? Why or why not?


 
 
 
 
 
 

Step 3. Update the main method of PointFun so that every line that read

    out.println("(" + pt.getX() + "," + pt.getY() + ")");

now reads

    out.println(pt.toString());

Recompile and execute PointFun. Record your results. Are they what you expected? Why or why not?


 
 
 
 
 
 

Step 4. In addition to inheriting the methods of its superclass, a subclass also inherits the fields of its superclass (subject to some restrictions that you do not yet need to understand). We can see this by updating the toString method to use xcoord and ycoord rather than getX and getY.

Replace the line in toString that reads

    return "x=" + getX() + ", y=" + getY();

with one that reads

    return "X:" + this.xcoord + ",Y:" + this.ycoord;

Recompile ExtendedPoint and execute PointFun. Record your results. Are they what you expected? Why or why not?


 
 
 
 
 
 

Step 5. The previous steps suggest that we often have the choice of using the methods of the superclass (e.g., getX) to obtain the fields of the superclass (e.g., xcoord) or directly using the fields of the superclass. Which strategy do you think is better and why? You may want to consider which of the toString methods you prefer in answering this question, but you should also try to think about other issues.


 
 
 
 
 
 

Experiment O2.4: Overriding methods

Required files:

In addition to adding new methods to subclasses (as in Experiment O2.3), we may also want to change what methods do. For example, we might want to create a variant of Points that use a difference distance metrics or a variant of ExtendedPoints that print points in a different way. We will make the distance extension in this experiment and the printing extension in the next experiment.

Step 1. Make copies of Point.java, PointFun.java, and TaxicabPoint.java. Compile all three files. Execute PointFun and record the results.


 
 
 
 
 
 

Step 2. Read the source code for Point and TaxicabPoint and, in your own words, describe how TaxicabPoints differ from Points.


 
 
 
 
 
 

Step 3. Update PointFun to use TaxicabPoints instead of regular Points. Compile and execute PointFun and record your results. Are they the same as in step 1? If so, why? If not, why not?


 
 
 
 
 
 

Experiment O2.5: Overriding the toString method

Required files:

Step 1. Make copies of Point.java, PointFun.java, and ExtendedPoint.java. If you have not already done so, add the following toString method to ExtendedPoint.

  /**
   * Convert the point to a string for ease of printing.
   */
  public String toString() {
    return "x=" + getX() + ", y=" + getY();
  } // toString()

Update the main method of PointFun to use ExtendedPoints and the toString method, as in

  /**
   * Build a point and move it around.
   */
  public static void main(String[] args) {
    // We'll be generating some output.
    SimpleOutput out = new SimpleOutput();
    // The point we'll be playing with.
    ExtendedPoint pt = new ExtendedPoint(2,3);
    // Print some basic information
    out.println(pt.toString());
    out.println("  distance from origin: " + 
                pt.distanceFromOrigin());
    // Move it right a little bit.
    out.println("Shifting right by 0.7");
    pt.shiftRight(0.7);
    // Print current information
    out.println(pt.toString());
    out.println("  distance from origin: " + 
                pt.distanceFromOrigin());
    // Move it right a little bit.
    out.println("Shifting up by 2.5");
    pt.shiftUp(2.5);
    // Print current information
    out.println(pt.toString());
    out.println("  distance from origin: " + 
                pt.distanceFromOrigin());
    // Move it left a little bit.
    out.println("Shifting left by 10.2");
    pt.shiftLeft(10.2);
    // Print current information
    out.println(pt.toString());
    out.println("  distance from origin: " + 
                pt.distanceFromOrigin());
  } // main(String[])

Compile the files. Execute PointFun and record the results.


 
 
 
 
 
 

Step 2. Build a new ReextendedPoint class containing only the following lines (along with an appropriate introductory comment and constructors).

public class ReextendedPoint
  extends ExtendedPoint {
} // class ReextendedPoint

Compile the new class and correct any errors. What methods do you expect ReextendedPoint to provide?


 
 
 
 
 
 

After you've answered this question, read the notes on this step.

Step 3. Update PointFun to use ReextendedPoints instead of ExtendedPoints. Recompile and execute PointFun. Record and explain the results.


 
 
 
 
 
 

Step 4. Add the following toString method to ReextendedPoint.

  /**
   * Convert the point to a string for ease of printing.
   */
  public String toString() {
    return "(" + getX() + "," + getY() + ")";
  } // toString()

Recompile ReextendedPoint and execute PointFun. Record and explain the results.


 
 
 
 
 
 

Experiment O2.6: From Rectangle to Square

Required files:

Step 1. Make a copy of Rectangle.java. Build a subclass of Rectangle called Square. You need not put anything in Square (yet). Build a class, SquareTester that you will use for testing your Square class. The main method of SquareTester should include

    SimpleOutput out = new SimpleOutput();
    Square box = new Square();
    out.println("The area of the square is: " + box.area());

Are you able to compile all three files? If not, why not? If so, record and explain the output.


 
 
 
 
 
 

Step 2. Add a constructor to Square that takes one parameter: the length of a side. Update SquareTester to use that constructor. Compile the various components of your program and correct any errors. Enter the code for your constructor here.


 
 
 
 
 
 

Step 3. Add the following line to the main method of SquareTester. Recompile SquareTester and record the results. Explain those results.

    box.setWidth(2);
    out.println("The height of the box is now: " + box.getHeight());


 
 
 
 
 
 

Step 4. Look through the code for Rectangle. Make a list of the methods it provides. Highlight the methods that you expect to need to change but don't change any methods


 
 
 
 
 
 

Step 5. In order to correctly set the width of a square, we'll need to override the setWidth method. But we also need to access the width and height in the Rectangle class, preferably with the setWidth and setHeight of that class. How do we distinguish the two versions of setWidth? We can use super.setWidth to indicate the appropriate method of the superclass.

Write a new setWidth method that updates both width and height, which is what should happen in a square. Enter the code for that method here.


 
 
 
 
 
 


Post-Laboratory Problems

Note: Most of these problems require conditionals.

Problem O2-A: Extending Points

Build ComparablePoint, a subclass of the Point class that includes equals(Point other) and lessThan(Point other) methods. The equals method should return true if the other point is equal to the current point. The lessThan method should return true if the current point is closer to the origin than the other point.

Write a small test program that uses your ComparablePoints.

Problem O2-B: Putting Points on a grid

Build GridPoint, a subclass of the Point class that puts points on a grid, rather than anywhere on the plane. For example, if the grid has a spacing of 1, then the point (0.7,2.3) is placed at (1,2). If the grid has a spacing of 0.5, then the point (1.2, 2.3) is placed at (0.5, 2.5). The constructor for your class should take the spacing as a parameter.

Write an appropriate program to test your new class.

Problem O2-C: Validating Squares

One of the drawbacks to the Square and Rectangle classes we developed in the experiments is that they do not verify that their width and height are reasonable. In particular, they will admit negative or zero-valued widths and heights, something we traditionally do not accept for squares and rectangles.

a. Develop a new SafeSquare class that extends the Square class.

b. Develop a new SafeRectangle class that extends the Rectangle class. Extend SafeRectangle to build a NewSafeSquare class.

c. Which of a and b is a better way of building a safe square class?

Problem O2-D: A hierarchy of shapes

Often, we use subclassing to build hierarchies of objects. For example, we might begin with Books, subclass them to Textbooks, Novels, and Collections (obviously not a comprehensive list of types of books). We could then subclass Textbooks to Introductory and Advanced or perhaps by discipline. Similarly, we might subclass Collections into ShortStories and Essays or perhaps into single author and multiple author collections. And so on and so forth.

Design a hierarchy of shapes. That is, begin with a generic Shape class including notes as to which methods it might provide, such as area. Then list the subclasses and sub-subclasses of the Shape class. At each stage, note which methods you may need to override.


Notes

Experiment O2.2, Step 2. The compilation of ExtendedPoint should be successful. If it isn't, you have most likely made a typographical error in entering the code for the new class.

Experiment O2.2, Step 4. It is likely that the Java compiler will complain that you are attempting to use a parameterized constructor for ExtendedPoint when no such constructor exists.

Experiment O2.5, Step 2. ReextendedPoint extends ExtendedPoint. This means that it automatically provides any methods that ExtendedPoint provides. ExtendedPoint extends Point, so it automatically provides all the methods that Point provides. ExtendedPoint also provides a toString method. Hence, ReextendedPoint provides all the methods of Point (getX, getY, etc.) plus toString.


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

Source text last modified Wed Oct 6 12:41:42 1999.

This page generated on Thu Dec 9 16:28:45 1999 by Siteweaver.

Contact our webmaster at rebelsky@math.grin.edu