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.
[Front Door] [Introduction] [Code]
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