Chapter 11. Subclasses
Every class in Java is built from another Java class. The new class
is called a
The subclass concept has important implications in Java, and we explore the concept in this chapter.
11.1. Fundamentals of subclasses
The GOval class is a good example of a subclass: It is a
subclass of the GObject class. The GObject class
represents abstract objects that might appear inside a graphics window,
and a GOval object is a particular shape that will appear.
Since all such objects will have a position in the window,
GObject defines
several methods regarding the object's position, including
getX, getY, and move. The GOval class, as a
subclass of GObject, inherits all of these methods, as well as
adding some of its own, like setFilled. The GLine class,
also a subclass of GObject, inherits GObject's methods
too, and it adds different methods, like setStartPoint and
setEndPoint for moving the line's endpoints. (The GLine
class does not have a method called setFilled.)
Subclasses are meant to be more specialized versions of the
superclass — hence the word subclass, similar to the word
subset from mathematics. Ovals are a subset of all shapes, so
GOval is defined as a subclass of GObject. Being more
specialized, it may make sense for the subclass to be perform
specialized methods that don't apply to the more general superclass.
The method setFilled doesn't make sense for GObject,
because for some
shapes (such as lines), the notion of being filled
is senseless.
But for ovals, it does make sense, and so GOval defines a
setFilled method.
Often we say that the new class
.)public class…extends GraphicsProgram
If we wanted a GCircle class for representing circles, then a
good designer would define it as a subclass of GOval,
since circles are simply a special type of oval. This class might add
some additional methods, such as getRadius, that don't make as
much sense in the context of ovals. (The designers of the
acm.graphics package didn't feel that circles were sufficiently
interesting to include a special class for them, though.) By the way,
GCircle would automatically be a subclass of GObject, too,
since every circle is an oval, and every oval is a shape.
Much like a family tree, classes can be arranged into a diagram called
an acm.graphics package, plus our
hypothetical GCircle class.
Figure 11.1: An inheritance hierarchy. (The italicized
GCircleis not in acm.graphics.)![]()
11.2. The Object class
This chapter began: Every class in Java is built from another
Java class.
This leads to an obvious question: Does this mean
that there are infinitely many classes, each extending the next
one?
Actually, the sentence wasn't entirely true. There is one special
class which does not extend any other class: Object, found in
the java.lang package.
When defining a class that doesn't
seem sensibly to extend any other class (as often happens),
a developer would define it to
extend the Object class alone. The GObject class is an example of a
class whose only superclass is Object.
The Object class does include a few methods.
Because Object is the ultimate parent of all other classes, all
other classes inherit these methods.
In this book, we'll examine
two of these methods, equals and toString.
boolean equals(Object other)Returns
trueif this object is identical toother. By default, the two objects are regarded as equal only if they are the same object. For some classes (notablyString),equalscompares the data within the object to check whether the objects represent the same concept, even if they are two separate objects.String toString()Returns a string representation of this object's value. By default, this string is basically nonsense, but for some subclasses the method returns a meaningful string representation.
These instance methods can be applied to any object in a program,
because every object is a member of some class, and that class must lie
somewhere below Object class in the inheritance hierarchy, so it
will inherit the Object instance methods.
Thus, writing
would be legal, no matter
what sort of object ball.toString()ball references.
11.3. Subclasses and variables
Any instance of a class A can also be treated as an
instance of a superclass of A. Thus, if B is a
superclass of A, then every
A object can also be treated as a B objects.
As a result, if a variable's type is a class C,
then the variable can reference an object whose actual class is some
subclass of C. For
example, we could rewrite our MovingBall class so that
ball is a GObject variable.
GObject ball; // Note that ball is a GObject
ball = new GOval(10, 10, 100, 100); // but we assign a GOval to it.
add(ball);
while(true) {
pause(40);
ball.move(1, 0);
}
The ball variable is declared to reference any GObject,
including instances of subclasses of GObject. So in the
second line, initializing ball to reference a GOval is
acceptable. We could also make ball refer to a GRect if
we liked, since GRect is also a subclass of GObject.
Later in the fragment, the line
is acceptable, because the
ball.move(1, 0);move method is defined in the GObject class. (The compiler
knows that this method will be inherited by any subclass, so
it's assured that even if ball's actual class is some subclass,
move will still be a legal method.)
On the other hand, a program cannot invoke a method not defined for
the class that the variable represents, as attempted below.
GObject ball;
ball = new GOval(75, 75, 50, 50);
ball.setFilled(true); // Illegal!!
Should this fragment execute, ball will of course refer to a
GOval, and one might think that invoking setFilled
should be legal.
But the compiler translates the program before execution,
and the compiler looks only at the statement in question
to determine whether a method invocation is legal.
Looking at the setFilled invocation,
the compiler knows only that ball references
a GObject object; it doesn't look at the rest of the program
to deduce the obvious fact that ball will actually be a
GOval. Since not all GObject
objects have a setFilled method, the compiler will reject
the line and the program.
You ought to be wondering: Why would I ever want a variable anyway
whose type is a superclass of the object that it will actually
reference? After all, using the superclass simply restricts what we can
do with the object. Most often, this reasoning is correct:
The program might as well use the most specific type available.
But there are some circumstances where using a more general type
is quite useful. A notable example is the add method in
the GraphicsProgram class.
void add(GObject shape)Adds
shapefor drawing within this window. Ifshape's class is written properly, any modifications toshapewill be reflected immediately in this window.
Using the above reasoning concerning variables,
the add method's parameter shape can refer to an
instance of any GObject subclass.
Thus, this single add method can deal with inserting
a GOval, a GRect, or any other subclass of GObject.
For the person writing GraphicsProgram,
writing add just once for all GObjects is much more
convenient than requiring an add
method for each possible category of shape. In fact, we can even
define our own subclasses of
GObject (as we'll do in Chapter 14), and the
add method will automatically apply to them, too, with no
need to modify GraphicsProgram.
11.4. Example: Psychedelic balls
To illustrate an instance where having a GObject variable is
useful, let's consider the following goal: Suppose we want a window
containing fifty circles, and we want them all to
switch colors every quarter second.
The obvious way to do it, based on what we've seen so far, is to create fifty different variables, one for each circle. With each quarter second, we'd change the color for the circle to which each variable refers. This way is quite cumbersome, and it involves lots of duplication of code, which is always something we should strive to avoid, since duplication inhibits debugging and enhancing programs.
A much simpler alternative is to use the following two methods from
the GraphicsProgram class.
GObject getElement(int index)Returns a
GObjectthat has been added into the window. The objects are numbered consecutively starting from 0.int getElementCount()Returns the number of
GObjects that are currently in this window.
These methods are useful because they
relieve us from the problem of
having to remember all the shapes within our program: We can instead
just ask the GraphicsProgram object to get each shape for us.
Note that getElement returns a GObject; this is because a
particular shape in the window can be of any subclass of
GObject.
Figure 11.2 contains a program accomplishing our task using this technique.
Figure 11.2: The
PsychedelicBallsprogram.1 import acm.program.*;
2 import acm.graphics.*;
3 import java.awt.*;
4
5 public class PsychedelicBalls extends GraphicsProgram {
6 public void run() {
7 // Add all the circles into the window
8 Color cur = Color.BLUE; // set up the colors to switch between
9 Color prev = Color.RED;
10 for(int row = 0; row < 5; row++) {
11 for(int col = 0; col < 10; col++) {
12 double x = (col + 0.5) * getWidth() / 10.0;
13 double y = (row + 0.5) * getHeight() / 5.0;
14 GOval ball = new GOval(x - 25, y - 25, 50, 50);
15 ball.setFilled(true);
16 ball.setColor(cur);
17 add(ball);
18 }
19 }
20
21 // Now repeatedly change out the colors
22 while(true) {
23 pause(250); // wait a quarter second
24
25 Color temp = cur; // swap colors for this frame
26 cur = prev;
27 prev = temp;
28
29 for(int i = 0; i < getElementCount(); i++) {
30 GObject shape = getElement(i);
31 shape.setColor(cur);
32 }
33 }
34 }
35 }
The program has two major sections.
The first, lines 7
to 19,
runs very quickly at the beginning to add 5 rows of 10 balls each into
the window.
The computer will spend the remainder of its time in the second part,
lines 7
to 19,
where it repeatedly waits for a quarter second,
swaps into the color for the next frame,
and then iterates through all the shapes in the window.
The important part for us is
line 30,
which retrieves each individual ball using the getElement
method.
Note
line 31.
It invokes setColor on each circle, rather than
setFillColor as we've been using in programs until now.
It would actually be illegal to invoke setFillColor here,
since shape is a GObject variable,
and setFillColor isn't defined in the GObject class.
However, GObject does define setColor,
so the program is legal as written.
(The difference between setColor and setFillColor is that
setColor also changes the color of the shape's boundary. However,
if setFillColor has been applied to the shape, then
setColor does not affect the shape's interior.)
11.5. Casting
But what if we want to use setFillColor in
line 31
of Figure 11.2? It seems like this should be legal:
After all, we already know that all the shapes in the window are
GOval objects, and GOval has a setFillColor
method.
In fact, Java does provide a way to accomplish this, using
with the following instead.shape.setColor(cur);
GOval ball = (GOval) shape; // the cast is on the right side of '='
ball.setFillColor(cur);
With each shape then, the cast will force shape into being
treated as a GOval. Our program can assign this into a
GOval variable such as ball, and then we can apply
setFillColor to this GOval variable.
In fact, since the ball variable is used only once, we can
combine the lines together. However, casting is a lower order of
precedence than the period operator (the method application operator),
just as addition is a lower order of precedence than multiplication in
the order of operations. Thus, if we combine the lines, we'll need to
enclose the cast in an extra set of parentheses.
((GOval) shape).setFillColor(cur);
If wrote
, omitting the
parentheses, then the compiler would interpret this as if we had written
(GOval) shape.setFillColor(cur);
. That is, the compiler
would think we mean for the computer to
invoke (GOval) (shape.setFillColor(cur));setFillColor on shape first and then to cast
whatever setFillColor returns into a GOval object.
The compiler will reject the line, because shape, as a
GObject, does not provide a setFillColor method.
In fact, we can abbreviate the loop body even further, because
the shape variable is also used only once. Thus, the entire loop
body can fit into one line.
((GOval) getElement(i)).setFillColor(cur);
For any of these alternative formulations, it's`worth wondering:
What if one of the shapes in the window isn't a GOval?
For example, what would happen if we happened to have a GRect
somewhere in the picture? In this case, the program would still compile
properly. But when the program executes, the computer would soon get to
the line where getElement returns the GRect, and
it is supposed to cast
that GRect into a GOval. At this point, the computer
will terminate the
program with an exception called a ClassCastException.
11.6. The instanceof operator
Sometimes you want to be able to test whether a variable references
an object of a particular class. Returning to our
PsychedelicBalls example of Figure 11.2,
suppose our picture also
included some GRects, which we did not want to flicker
between colors. To accomplish this, it would be helpful if, as we
iterate through all the shapes of the window in
lines 29
to 32, we could test which of the
shapes are GOvals and which are GRects.
The solution is the instanceof operator. On the left side of
this operator is some object, and on its right side is the name of some
class. Upon reaching the operator, the computer determines whether the
left-side object is an instance of the right-side class, and it
substitutes true or false accordingly. We can thus place
this operator inside an if statement to determine whether to
attempt the cast and invocation of setFillColor.
GObject shape = getElement(i);
if(shape instanceof GOval) {
((GOval) shape).setFillColor(cur);
}
With this modification, the setFillColor line will be
attempted only for
the GOval objects. This will avoid the potential for the
ClassCastException that would otherwise occur if the cast within
the line were attempted for GRects or GLines.
Of course, if the left-side
object is an instance of some subclass (or subsubclass, …) of the
right-side class, it would
still be considered an instance of the right-side
class. Thus, if the window contained an instance of our hypothetical
GCircle class, it too would flicker between colors.
