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 TaxicabPoint
s. 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.
ExtendedPoint
sRequired 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 ExtendedPoint
s rather than
Point
s. 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
Point
s that use a difference distance metrics or a
variant of ExtendedPoint
s 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 TaxicabPoint
s differ from
Point
s.
Step 3.
Update PointFun
to use TaxicabPoint
s instead of
regular Point
s. 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 ExtendedPoint
s 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 ReextendedPoint
s instead
of ExtendedPoint
s. 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 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.
Note: Most of these problems require conditionals.
Point
s
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 ComparablePoint
s.
Point
s 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.
Square
s
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 Book
s, subclass them to
Textbook
s, Novel
s, and
Collection
s (obviously not a comprehensive list of types of
books). We could then subclass Textbook
s to
Introductory
and Advanced
or perhaps by
discipline. Similarly, we might subclass Collection
s 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