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:
NewPoint.java
Point.java
PointFun.java
Rectangle.java
SimpleInput.java
SimpleOutput.java
TaxicabPoint.java
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).
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.
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.
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.
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.
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.
Name: ________________
ID:_______________
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.
Point classRequired 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.
ExtendedPointsRequired files:
Point.java
PointFun.java
ExtendedPoint.java (created in the previous experiment)
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.
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?
toString
methodRequired files:
Point.java
PointFun.java
ExtendedPoint.java (created in the previous experiment)
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.
Rectangle to SquareRequired 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.
Note: Most of these problems require conditionals.
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.
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.
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?
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.
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.
[Front Door] [Introduction] [Code]
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