You may recall from the discussion of inheritance that inheritance
provides an elegant mechanism for reuse. In particular, by extending a
class, you automatically reuse all the methods in that class with no
additional effort. But are there other ways in which we may want to
reuse code? Yes! Often, we would like to write general methods that
will work on a variety of related objects. For example, we might design
a printIndented
method that should print any object or
at least any printable object, indented by an appropriate number of
spaces. Similarly, we might design a sort
method that can
sort any collection of elements or at least any
collection of elements in which we can compare the individual elements.
In some languages, it is impossible or exceedingly difficult to design such methods. Fortunately, Java's inheritance mechanism and an accompanying feature called interfaces simplify the design of general methods. Together with polymorphism, interface and inheritance support generalized methods.
What is polymorphism? It seems that there are hundreds of related definitions. You can think of polymorphism as the ability to use an object in a subclass in place of an object in a superclass, with the choice of methods to execute based on the actual class of the object.
For example, consider the SimpleDate
class which we have used
in the past. Here is a sample implementation of such a class.
/** * A very simple implementation of dates using Gregorian-style * calendars (with year, month, and day). * * @author Samuel A. Rebelsky * @version 1.2 of September 1998 */ public class SimpleDate { // +--------+-------------------------------------------------- // | Fields | // +--------+ /** The year. */ protected int year; /** The month. Use 1 for January, 2 for February, ... */ protected int month; /** The day in the month. */ protected int day; // +--------------+-------------------------------------------- // | Constructors | // +--------------+ /** * Build a new date with year, month, and day. The month should be * between 1 and 12 and the day between 1 and the number of days in * the month. */ public SimpleDate(int y, int m, int d) { this.year = y; this.month = m; this.day = d; } // SimpleDate(int,int,int) // +------------+---------------------------------------------- // | Extractors | // +------------+ /** * Get the year. */ public int getYear() { return this.year; } // getYear() /** * Get the month. */ public int getMonth() { return this.month; } // getMonth() /** * Get the day. */ public int getDay() { return this.day; } // getDay() /** * Convert to a string (American format: MM/DD/YYYY) */ public String toString() { return this.month + "/" + this.day + "/" + this.year; } // toString() } // class SimpleDate
Suppose we want to subclass SimpleDate
to provide more
precise time information including not just the day, month, and year, but
also minute and second on that day. Here is a class that does just that.
/** * Extended time/date information, including not just the day, but also the * time of day. (Alternately, including not just the time, but the day on * which that time falls.) * * @author Samuel A. Rebelsky * @version 1.0 of January 1999 */ public class SimpleTime extends SimpleDate { // +--------+--------------------------------------------------- // | Fields | // +--------+ /** The hour of the day. */ public int hour; /** The minute of the day. */ public int minute; // +--------------+--------------------------------------------- // | Constructors | // +--------------+ /** * Create a new time, specifying all the components. */ public SimpleTime(int year, int month, int day, int hour, int minute) { // Set up the day without the time super(year,month,day); // Fill in the remaining information this.hour = hour; this.minute = minute; } // SimpleTime(int,int,int,int,int) /** * Create a new time, specifying only the day. Uses 0:00 as the hour and * minute. */ public SimpleTime(int year, int month, int day) { // Set up the day without the time super(year,month,day); // Fill in the remaining information this.hour = 0; this.minute = 0; } // SimpleTime(int,int,int) // +------------+----------------------------------------------- // | Extractors | // +------------+ /** * Get the hour. */ public int getHour() { return this.hour; } // getHour() /** * Get the minute. */ public int getMinute() { return this.minute; } // getMinute() /** * Convert to a string. */ public String toString() { if (this.minute < 10) { return this.hour + ":0" + this.minute + "," + super.toString(); } // if the minute is a single digit else { return this.hour + ":" + this.minute + "," + super.toString(); } // if the minute is multiple digits } // toString() } // class SimpleTime
The calls to super(year,month,day)
in the
constructors are calls to the constructors of the superclass. As you may
recall, the constructors in a subclass must begin with an explicit or implicit
call to a constructor for the superclass.
The call to super.toString
in the new
toString
says to use the toString
method of the
superclass, giving a string for the year, month, and day.
Now suppose a programmer wants to write a method to print a range of dates.
The method might print the first date, a dash, and then the second date. The
method might print the word ``from'', the first date, the word ``to'', and the
second date. Here is some code that might do the latter. (You can find this
code in DateHelper.java
.)
public void printRange(SimpleOutput out, SimpleDate first, SimpleDate second) { out.println("From " + first.toString() + " to " + second.toString()); } // printRange(SimpleOutput,SimpleDate,SimpleDate)
What happens if we call this with two SimpleDates
, as in
helper.printRange(out, new SimpleDate(1964,6,17), new SimpleDate(1964,12,3));
As you might expect, it prints the range using just the dates, giving
From 6/17/1964 to 12/3/1964
What happens if we call this with two SimpleTime
s, as in
helper.printRange(out, new SimpleTime(1964,6,17), new SimpleTime(1964,12,3));
Even though printRange
expects two SimpleDate
s,
it ends up using the toString
method of SimpleTime
.
The output will then be
From 0:00, 6/17/1964 to 0:00, 12/3/1964
In some object-oriented languages (such as C++), the default action would have
been for
printRange
to use SimpleDate
's toString
method in both cases.
Polymorphism is very important. In fact, many computer scientists consider polymorphism one of the three key building blocks of object-oriented programming, the three being objects, inheritance, and polymorphism.
In Experiment O3.1 you will experiment with polymorphism.
As the asides in the introduction suggest, it would be nice if we
could just say this method works on any object that provides
these methods. For example, printIndented
works on any object that provides getX
and
getY
methods. It would seem that polymorphism, or something
similar, should support such descriptions.
Let us consider the print
method provided by the
PointPrinter
class. You may recall that that method is
defined as
out.println("(" + pt.getX() + "," + pt.getY() + ")"); out.println(" distance from origin: " + pt.distanceFromOrigin());
(While this method might be improved by using the toString
method developed in a previous lab, note that by default
Point
s do not include such a method.)
It seems that this method should be able to work on any class that
provides getX
, getY
, and
distanceFromOrigin
methods. However, as you will see in Experiment O3.2, this is not the case.
In particular, Java's type-checking mechanism, which verifies
that when you call a method, the arguments match the types of the
parameters to the method, will prevent you from using a
NewPoint
where a Point
is called for. That
is, even though NewPoint
provides the same methods as
Point
the type-checking system is unable or unwilling to
check this.
At the same time, Java needs some way to provide generalized methods,
since the solution of rewrite the method with the same code but a
different method header is not only inelegant, but also not always
possible. As a simple example, consider the Plane
or
TextPlane
classes used in the introductory laboratories.
If we only had access to the files Plane.class
or
TextPlane.class
and not to Plane.java
or
TextPlane.java
, then there would be no way to plot
NewPoint
s without first converting them to
Point
s.
As you begin to use more code written by others, you will soon learn that a large number of professional programmers only distribute their compiled code and not their source code. For example, Microsoft will be happy to sell you a copy of the executable (i.e., compiled) versions of Word or Excel, but you will be unable to convince them to let you see the inner workings of the programs (i.e., their source code). At the same time, it is becoming increasingly likely that people will want to extend large programs with custom components, such as a new drawing tool to be used in a drawing program.
Fortunately, Java provides two mechanisms for writing generalized functions: inheritance and interfaces.
The designers of Java decided that successful programming more formal
mechanisms for indicating that a class has certain capabilities.
Since every method provided by a class is automatically inherited
or overridden by the subclass, inheritance provides a simple and elegant
way for indicating just this. In particular,
Java permits you to use an object from a subclass
wherever an object for the superclass is called for. Hence, because
Plane
and TextPlane
provide a
plot(Point)
methods, you can also plot
ExtendedPoint
s. Similarly, because PointPrinter
provides a print(Point)
method, you can print
ExtendedPoint
s.
You will investigate this type of reuse in Experiment O3.3.
As you've seen, there are two reasons to use inheritance. First, by
extending another class, we can automatically provide all the methods of
the class. Second, we can use a member of a subclass anywhere we can
use a member of the superclass. We can take advantage of this second
property to build generalized methods that act on a variety of objects.
For example, Plane
can plot anything that acts like a
point.
What other generalized methods might we build? Typically, sorting and searching methods are written so that they can sort or search sequences of a wide variety of types. As long as you can compare two values, you can search or sort.
On a more practical level, we might want to improve SimpleOutput
(or MyOutput
, if you've done Lab X4) to print a wider variety
of values, not just strings and numbers, but also other objects. For
example, we might define a class, Printable
, that includes
a makePrintabale
method used to convert the current object to a
string. We can then say that it is possible to print any subclass
of Printable
using the following method
/** * Print a printable object. */ public void print(Printable printme) { this.print(printme.makePrintable()); } // print(Printable)
Unfortunately, it is awkward to use Printable
. In
particular, what should the default makePrintable
method (the
one provided by Printable
) do? More importantly, what
happens if we want to subclass a nonprintable class, such as
Point
and also make a printable class. As mentioned in the
previous laboratory session, Java does not permit you to subclass two
classes.
Java provides an alternate mechanism for writing and using generalized functions. Rather than writing a superclass, you can instead write an interface. An interface definition looks surprisingly like a class definition except that
interface
is used instead
of the keyword class
, and
With inheritance, you indicate that a class extends
another class. With interfaces, you indicate that a class
implements
another class. For example, we might
indicate that a PrintablePoint
implements the
Printable
interface with
public class PrintablePoint implements Printable
An interface is a contract between you and the compiler in which you assert that I have implemented all of the methods defined in the interface, and the compiler can trust your class to implement all of the methods declared in the interface.
You use interfaces similarly to the way you use superclasses except that,
For example, we can define a Printable
interface as follows
/** * A simple interface used to describe classes that include a * toString method (and are therefore printable). * * @author Samuel A. Rebelsky * @version 1.0 of September 1998 */ public interface Printable { /** * Convert the current object to a string. */ public String makePrintable(); } // interface Printable
We can now declare a printable point (i.e., something that extends
Point
but is also printable) with
/** * A point that can be printed. */ public class PrintablePoint extends Point implements Printable { } // class PrintablePoint
In Experiment O3.4, you will
improve SimpleOutput
by permitting it to print any
class that implements Printable
.
In Experiment O3.5, you will perform other
comparisons of interfaces and superclasses.
[Front Door] [Introduction] [Code]
Copyright (c) 1998 Samuel A. Rebelsky. All rights reserved.
Source text last modified Tue Oct 26 13:29:31 1999.
This page generated on Tue Oct 26 15:38:05 1999 by Siteweaver.
Contact our webmaster at rebelsky@math.grin.edu