ZeePedia

Interfaces & Inner Classes:Extending an interface with inheritance, Inner class identifiers

<< Polymorphism:Upcasting revisited, The twist, Designing with inheritance
Holding Your Objects:Container disadvantage, List functionality, Map functionality >>
img
}
class Wind extends Instrument {
public void play() {
System.out.println("Wind.play()");
}
public String what() { return "Wind"; }
public void adjust() {}
}
class Percussion extends Instrument {
public void play() {
System.out.println("Percussion.play()");
}
public String what() { return "Percussion"; }
public void adjust() {}
}
class Stringed extends Instrument {
public void play() {
System.out.println("Stringed.play()");
}
public String what() { return "Stringed"; }
public void adjust() {}
}
class Brass extends Wind {
public void play() {
System.out.println("Brass.play()");
}
public void adjust() {
System.out.println("Brass.adjust()");
}
}
class Woodwind extends Wind {
public void play() {
System.out.println("Woodwind.play()");
}
public String what() { return "Woodwind"; }
}
322
Thinking in Java
img
public class Music3 {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument i) {
// ...
i.play();
}
static void tuneAll(Instrument[] e) {
for(int i = 0; i < e.length; i++)
tune(e[i]);
}
public static void main(String[] args) {
Instrument[] orchestra = new Instrument[5];
int i = 0;
// Upcasting during addition to the array:
orchestra[i++] = new Wind();
orchestra[i++] = new Percussion();
orchestra[i++] = new Stringed();
orchestra[i++] = new Brass();
orchestra[i++] = new Woodwind();
tuneAll(orchestra);
}
} ///:~
The new methods are what( ), which returns a String reference with a
description of the class, and adjust( ), which provides some way to adjust
each instrument.
In main( ), when you place something inside the Instrument array you
automatically upcast to Instrument.
You can see that the tune( ) method is blissfully ignorant of all the code
changes that have happened around it, and yet it works correctly. This is
exactly what polymorphism is supposed to provide. Your code changes
don't cause damage to parts of the program that should not be affected.
Put another way, polymorphism is one of the most important techniques
that allow the programmer to "separate the things that change from the
things that stay the same."
Chapter 7: Polymorphism
323
img
Overriding vs. overloading
Let's take a different look at the first example in this chapter. In the
following program, the interface of the method play( ) is changed in the
process of overriding it, which means that you haven't overridden the
method, but instead overloaded it. The compiler allows you to overload
methods so it gives no complaint. But the behavior is probably not what
you want. Here's the example:
//: c07:WindError.java
// Accidentally changing the interface.
class NoteX {
public static final int
MIDDLE_C = 0, C_SHARP = 1, C_FLAT = 2;
}
class InstrumentX {
public void play(int NoteX) {
System.out.println("InstrumentX.play()");
}
}
class WindX extends InstrumentX {
// OOPS! Changes the method interface:
public void play(NoteX n) {
System.out.println("WindX.play(NoteX n)");
}
}
public class WindError {
public static void tune(InstrumentX i) {
// ...
i.play(NoteX.MIDDLE_C);
}
public static void main(String[] args) {
WindX flute = new WindX();
tune(flute); // Not the desired behavior!
}
} ///:~
324
Thinking in Java
img
There's another confusing aspect thrown in here. In InstrumentX, the
play( ) method takes an int that has the identifier NoteX. That is, even
though NoteX is a class name, it can also be used as an identifier without
complaint. But in WindX, play( ) takes a NoteX reference that has an
identifier n. (Although you could even say play(NoteX NoteX) without
an error.) Thus it appears that the programmer intended to override
play( ) but mistyped the method a bit. The compiler, however, assumed
that an overload and not an override was intended. Note that if you follow
the standard Java naming convention, the argument identifier would be
noteX (lowercase `n'), which would distinguish it from the class name.
In tune, the InstrumentX i is sent the play( ) message, with one of
NoteX's members (MIDDLE_C) as an argument. Since NoteX contains
int definitions, this means that the int version of the now-overloaded
play( ) method is called, and since that has not been overridden the base-
class version is used.
The output is:
InstrumentX.play()
This certainly doesn't appear to be a polymorphic method call. Once you
understand what's happening, you can fix the problem fairly easily, but
imagine how difficult it might be to find the bug if it's buried in a program
of significant size.
Abstract classes
and methods
In all the instrument examples, the methods in the base class
Instrument were always "dummy" methods. If these methods are ever
called, you've done something wrong. That's because the intent of
Instrument is to create a common interface for all the classes derived
from it.
The only reason to establish this common interface is so it can be
expressed differently for each different subtype. It establishes a basic
form, so you can say what's in common with all the derived classes.
Chapter 7: Polymorphism
325
img
Another way of saying this is to call Instrument an abstract base class
(or simply an abstract class). You create an abstract class when you want
to manipulate a set of classes through this common interface. All derived-
class methods that match the signature of the base-class declaration will
be called using the dynamic binding mechanism. (However, as seen in the
last section, if the method's name is the same as the base class but the
arguments are different, you've got overloading, which probably isn't what
you want.)
If you have an abstract class like Instrument, objects of that class almost
always have no meaning. That is, Instrument is meant to express only
the interface, and not a particular implementation, so creating an
Instrument object makes no sense, and you'll probably want to prevent
the user from doing it. This can be accomplished by making all the
methods in Instrument print error messages, but that delays the
information until run-time and requires reliable exhaustive testing on the
user's part. It's always better to catch problems at compile-time.
Java provides a mechanism for doing this called the abstract method1.
This is a method that is incomplete; it has only a declaration and no
method body. Here is the syntax for an abstract method declaration:
abstract void f();
A class containing abstract methods is called an abstract class. If a class
contains one or more abstract methods, the class must be qualified as
abstract. (Otherwise, the compiler gives you an error message.)
If an abstract class is incomplete, what is the compiler supposed to do
when someone tries to make an object of that class? It cannot safely create
an object of an abstract class, so you get an error message from the
compiler. This way the compiler ensures the purity of the abstract class,
and you don't need to worry about misusing it.
If you inherit from an abstract class and you want to make objects of the
new type, you must provide method definitions for all the abstract
methods in the base class. If you don't (and you may choose not to), then
1 For C++ programmers, this is the analogue of C++'s pure virtual function.
326
Thinking in Java
img
the derived class is also abstract and the compiler will force you to qualify
that class with the abstract keyword.
It's possible to create a class as abstract without including any abstract
methods. This is useful when you've got a class in which it doesn't make
sense to have any abstract methods, and yet you want to prevent any
instances of that class.
The Instrument class can easily be turned into an abstract class. Only
some of the methods will be abstract, since making a class abstract
doesn't force you to make all the methods abstract. Here's what it looks
like:
abstract Instrument
abstract void play();
String what() { /* ... */ }
abstract void adjust();
extends
extends
extends
Wind
Percussion
Stringed
void play()
void play()
void play()
String what()
String what()
String what()
void adjust()
void adjust()
void adjust()
extends
extends
Woodwind
Brass
void play()
void play()
String what()
void adjust()
Here's the orchestra example modified to use abstract classes and
methods:
//: c07:music4:Music4.java
Chapter 7: Polymorphism
327
img
// Abstract classes and methods.
import java.util.*;
abstract class Instrument {
int i; // storage allocated for each
public abstract void play();
public String what() {
return "Instrument";
}
public abstract void adjust();
}
class Wind extends Instrument {
public void play() {
System.out.println("Wind.play()");
}
public String what() { return "Wind"; }
public void adjust() {}
}
class Percussion extends Instrument {
public void play() {
System.out.println("Percussion.play()");
}
public String what() { return "Percussion"; }
public void adjust() {}
}
class Stringed extends Instrument {
public void play() {
System.out.println("Stringed.play()");
}
public String what() { return "Stringed"; }
public void adjust() {}
}
class Brass extends Wind {
public void play() {
System.out.println("Brass.play()");
}
public void adjust() {
328
Thinking in Java
img
System.out.println("Brass.adjust()");
}
}
class Woodwind extends Wind {
public void play() {
System.out.println("Woodwind.play()");
}
public String what() { return "Woodwind"; }
}
public class Music4 {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument i) {
// ...
i.play();
}
static void tuneAll(Instrument[] e) {
for(int i = 0; i < e.length; i++)
tune(e[i]);
}
public static void main(String[] args) {
Instrument[] orchestra = new Instrument[5];
int i = 0;
// Upcasting during addition to the array:
orchestra[i++] = new Wind();
orchestra[i++] = new Percussion();
orchestra[i++] = new Stringed();
orchestra[i++] = new Brass();
orchestra[i++] = new Woodwind();
tuneAll(orchestra);
}
} ///:~
You can see that there's really no change except in the base class.
It's helpful to create abstract classes and methods because they make the
abstractness of a class explicit, and tell both the user and the compiler
how it was intended to be used.
Chapter 7: Polymorphism
329
img
Constructors and
polymorphism
As usual, constructors are different from other kinds of methods. This is
also true when polymorphism is involved. Even though constructors are
not polymorphic (although you can have a kind of "virtual constructor," as
you will see in Chapter 12), it's important to understand the way
constructors work in complex hierarchies and with polymorphism. This
understanding will help you avoid unpleasant entanglements.
Order of constructor calls
The order of constructor calls was briefly discussed in Chapter 4 and
again in Chapter 6, but that was before polymorphism was introduced.
A constructor for the base class is always called in the constructor for a
derived class, chaining up the inheritance hierarchy so that a constructor
for every base class is called. This makes sense because the constructor
has a special job: to see that the object is built properly. A derived class
has access to its own members only, and not to those of the base class
(whose members are typically private). Only the base-class constructor
has the proper knowledge and access to initialize its own elements.
Therefore, it's essential that all constructors get called, otherwise the
entire object wouldn't be constructed. That's why the compiler enforces a
constructor call for every portion of a derived class. It will silently call the
default constructor if you don't explicitly call a base-class constructor in
the derived-class constructor body. If there is no default constructor, the
compiler will complain. (In the case where a class has no constructors, the
compiler will automatically synthesize a default constructor.)
Let's take a look at an example that shows the effects of composition,
inheritance, and polymorphism on the order of construction:
//: c07:Sandwich.java
// Order of constructor calls.
class Meal {
Meal() { System.out.println("Meal()"); }
330
Thinking in Java
img
}
class Bread {
Bread() { System.out.println("Bread()"); }
}
class Cheese {
Cheese() { System.out.println("Cheese()"); }
}
class Lettuce {
Lettuce() { System.out.println("Lettuce()"); }
}
class Lunch extends Meal {
Lunch() { System.out.println("Lunch()");}
}
class PortableLunch extends Lunch {
PortableLunch() {
System.out.println("PortableLunch()");
}
}
class Sandwich extends PortableLunch {
Bread b = new Bread();
Cheese c = new Cheese();
Lettuce l = new Lettuce();
Sandwich() {
System.out.println("Sandwich()");
}
public static void main(String[] args) {
new Sandwich();
}
} ///:~
This example creates a complex class out of other classes, and each class
has a constructor that announces itself. The important class is
Sandwich, which reflects three levels of inheritance (four, if you count
the implicit inheritance from Object) and three member objects. When a
Sandwich object is created in main( ), the output is:
Chapter 7: Polymorphism
331
img
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()
This means that the order of constructor calls for a complex object is as
follows:
1.
The base-class constructor is called. This step is repeated
recursively such that the root of the hierarchy is constructed first,
followed by the next-derived class, etc., until the most-derived class
is reached.
2.
Member initializers are called in the order of declaration.
3.
The body of the derived-class constructor is called.
The order of the constructor calls is important. When you inherit, you
know all about the base class and can access any public and protected
members of the base class. This means that you must be able to assume
that all the members of the base class are valid when you're in the derived
class. In a normal method, construction has already taken place, so all the
members of all parts of the object have been built. Inside the constructor,
however, you must be able to assume that all members that you use have
been built. The only way to guarantee this is for the base-class constructor
to be called first. Then when you're in the derived-class constructor, all
the members you can access in the base class have been initialized.
"Knowing that all members are valid" inside the constructor is also the
reason that, whenever possible, you should initialize all member objects
(that is, objects placed in the class using composition) at their point of
definition in the class (e.g., b, c, and l in the example above). If you follow
this practice, you will help ensure that all base class members and
member objects of the current object have been initialized. Unfortunately,
this doesn't handle every case, as you will see in the next section.
332
Thinking in Java
img
Inheritance and finalize( )
When you use composition to create a new class, you never worry about
finalizing the member objects of that class. Each member is an
independent object, and thus is garbage collected and finalized regardless
of whether it happens to be a member of your class. With inheritance,
however, you must override finalize( ) in the derived class if you have
any special cleanup that must happen as part of garbage collection. When
you override finalize( ) in an inherited class, it's important to remember
to call the base-class version of finalize( ), since otherwise the base-class
finalization will not happen. The following example proves this:
//: c07:Frog.java
// Testing finalize with inheritance.
class DoBaseFinalization {
public static boolean flag = false;
}
class Characteristic {
String s;
Characteristic(String c) {
s = c;
System.out.println(
"Creating Characteristic " + s);
}
protected void finalize() {
System.out.println(
"finalizing Characteristic " + s);
}
}
class LivingCreature {
Characteristic p =
new Characteristic("is alive");
LivingCreature() {
System.out.println("LivingCreature()");
}
protected void finalize() throws Throwable {
System.out.println(
"LivingCreature finalize");
Chapter 7: Polymorphism
333
img
// Call base-class version LAST!
if(DoBaseFinalization.flag)
super.finalize();
}
}
class Animal extends LivingCreature {
Characteristic p =
new Characteristic("has heart");
Animal() {
System.out.println("Animal()");
}
protected void finalize() throws Throwable {
System.out.println("Animal finalize");
if(DoBaseFinalization.flag)
super.finalize();
}
}
class Amphibian extends Animal {
Characteristic p =
new Characteristic("can live in water");
Amphibian() {
System.out.println("Amphibian()");
}
protected void finalize() throws Throwable {
System.out.println("Amphibian finalize");
if(DoBaseFinalization.flag)
super.finalize();
}
}
public class Frog extends Amphibian {
Frog() {
System.out.println("Frog()");
}
protected void finalize() throws Throwable {
System.out.println("Frog finalize");
if(DoBaseFinalization.flag)
super.finalize();
}
334
Thinking in Java
img
public static void main(String[] args) {
if(args.length != 0 &&
args[0].equals("finalize"))
DoBaseFinalization.flag = true;
else
System.out.println("Not finalizing bases");
new Frog(); // Instantly becomes garbage
System.out.println("Bye!");
// Force finalizers to be called:
System.gc();
}
} ///:~
The class DoBaseFinalization simply holds a flag that indicates to each
class in the hierarchy whether to call super.finalize( ). This flag is set
based on a command-line argument, so you can view the behavior with
and without base-class finalization.
Each class in the hierarchy also contains a member object of class
Characteristic. You will see that regardless of whether the base class
finalizers are called, the Characteristic member objects are always
finalized.
Each overridden finalize( ) must have access to at least protected
members since the finalize( ) method in class Object is protected and
the compiler will not allow you to reduce the access during inheritance.
("Friendly" is less accessible than protected.)
In Frog.main( ), the DoBaseFinalization flag is configured and a
single Frog object is created. Remember that garbage collection--and in
particular finalization--might not happen for any particular object, so to
enforce this, the call to System.gc( ) triggers garbage collection, and
thus finalization. Without base-class finalization, the output is:
Not finalizing bases
Creating Characteristic is alive
LivingCreature()
Creating Characteristic has heart
Animal()
Creating Characteristic can live in water
Amphibian()
Chapter 7: Polymorphism
335
img
Frog()
Bye!
Frog finalize
finalizing Characteristic is alive
finalizing Characteristic has heart
finalizing Characteristic can live in water
You can see that, indeed, no finalizers are called for the base classes of
Frog (the member objects are finalized, as you would expect). But if you
add the "finalize" argument on the command line, you get:
Creating Characteristic is alive
LivingCreature()
Creating Characteristic has heart
Animal()
Creating Characteristic can live in water
Amphibian()
Frog()
bye!
Frog finalize
Amphibian finalize
Animal finalize
LivingCreature finalize
finalizing Characteristic is alive
finalizing Characteristic has heart
finalizing Characteristic can live in water
Although the order the member objects are finalized is the same order
that they are created, technically the order of finalization of objects is
unspecified. With base classes, however, you have control over the order
of finalization. The best order to use is the one that's shown here, which is
the reverse of the order of initialization. Following the form that's used in
C++ for destructors, you should perform the derived-class finalization
first, then the base-class finalization. That's because the derived-class
finalization could call some methods in the base class that require that the
base-class components are still alive, so you must not destroy them
prematurely.
336
Thinking in Java
img
Behavior of polymorphic methods
inside constructors
The hierarchy of constructor calls brings up an interesting dilemma. What
happens if you're inside a constructor and you call a dynamically bound
method of the object being constructed? Inside an ordinary method you
can imagine what will happen--the dynamically bound call is resolved at
run-time because the object cannot know whether it belongs to the class
that the method is in or some class derived from it. For consistency, you
might think this is what should happen inside constructors.
This is not exactly the case. If you call a dynamically bound method inside
a constructor, the overridden definition for that method is used. However,
the effect can be rather unexpected, and can conceal some difficult-to-find
bugs.
Conceptually, the constructor's job is to bring the object into existence
(which is hardly an ordinary feat). Inside any constructor, the entire
object might be only partially formed--you can know only that the base-
class objects have been initialized, but you cannot know which classes are
inherited from you. A dynamically bound method call, however, reaches
"outward" into the inheritance hierarchy. It calls a method in a derived
class. If you do this inside a constructor, you call a method that might
manipulate members that haven't been initialized yet--a sure recipe for
disaster.
You can see the problem in the following example:
//: c07:PolyConstructors.java
// Constructors and polymorphism
// don't produce what you might expect.
abstract class Glyph {
abstract void draw();
Glyph() {
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
Chapter 7: Polymorphism
337
img
class RoundGlyph extends Glyph {
int radius = 1;
RoundGlyph(int r) {
radius = r;
System.out.println(
"RoundGlyph.RoundGlyph(), radius = "
+ radius);
}
void draw() {
System.out.println(
"RoundGlyph.draw(), radius = " + radius);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
} ///:~
In Glyph, the draw( ) method is abstract, so it is designed to be
overridden. Indeed, you are forced to override it in RoundGlyph. But
the Glyph constructor calls this method, and the call ends up in
RoundGlyph.draw( ), which would seem to be the intent. But look at
the output:
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
When Glyph's constructor calls draw( ), the value of radius isn't even
the default initial value 1. It's 0. This would probably result in either a dot
or nothing at all being drawn on the screen, and you'd be left staring,
trying to figure out why the program won't work.
The order of initialization described in the previous section isn't quite
complete, and that's the key to solving the mystery. The actual process of
initialization is:
338
Thinking in Java
img
1.
The storage allocated for the object is initialized to binary zero
before anything else happens.
2.
The base-class constructors are called as described previously. At
this point, the overridden draw( ) method is called (yes, before the
RoundGlyph constructor is called), which discovers a radius
value of zero, due to step 1.
3.
Member initializers are called in the order of declaration.
4.
The body of the derived-class constructor is called.
There's an upside to this, which is that everything is at least initialized to
zero (or whatever zero means for that particular data type) and not just
left as garbage. This includes object references that are embedded inside a
class via composition, which become null. So if you forget to initialize
that reference you'll get an exception at run-time. Everything else gets
zero, which is usually a telltale value when looking at output.
On the other hand, you should be pretty horrified at the outcome of this
program. You've done a perfectly logical thing, and yet the behavior is
mysteriously wrong, with no complaints from the compiler. (C++
produces more rational behavior in this situation.) Bugs like this could
easily be buried and take a long time to discover.
As a result, a good guideline for constructors is, "Do as little as possible to
set the object into a good state, and if you can possibly avoid it, don't call
any methods." The only safe methods to call inside a constructor are those
that are final in the base class. (This also applies to private methods,
which are automatically final.) These cannot be overridden and thus
cannot produce this kind of surprise.
Designing with inheritance
Once you learn about polymorphism, it can seem that everything ought to
be inherited because polymorphism is such a clever tool. This can burden
your designs; in fact if you choose inheritance first when you're using an
existing class to make a new class, things can become needlessly
complicated.
Chapter 7: Polymorphism
339
img
A better approach is to choose composition first, when it's not obvious
which one you should use. Composition does not force a design into an
inheritance hierarchy. But composition is also more flexible since it's
possible to dynamically choose a type (and thus behavior) when using
composition, whereas inheritance requires an exact type to be known at
compile-time. The following example illustrates this:
//: c07:Transmogrify.java
// Dynamically changing the behavior of
// an object via composition.
abstract class Actor {
abstract void act();
}
class HappyActor extends Actor {
public void act() {
System.out.println("HappyActor");
}
}
class SadActor extends Actor {
public void act() {
System.out.println("SadActor");
}
}
class Stage {
Actor a = new HappyActor();
void change() { a = new SadActor(); }
void go() { a.act(); }
}
public class Transmogrify {
public static void main(String[] args) {
Stage s = new Stage();
s.go(); // Prints "HappyActor"
s.change();
s.go(); // Prints "SadActor"
}
} ///:~
340
Thinking in Java
img
A Stage object contains a reference to an Actor, which is initialized to a
HappyActor object. This means go( ) produces a particular behavior.
But since a reference can be rebound to a different object at run-time, a
reference for a SadActor object can be substituted in a and then the
behavior produced by go( ) changes. Thus you gain dynamic flexibility at
run-time. (This is also called the State Pattern. See Thinking in Patterns
with Java, downloadable at .) In contrast, you can't
decide to inherit differently at run-time; that must be completely
determined at compile-time.
A general guideline is "Use inheritance to express differences in behavior,
and fields to express variations in state." In the above example, both are
used: two different classes are inherited to express the difference in the
act( ) method, and Stage uses composition to allow its state to be
changed. In this case, that change in state happens to produce a change in
behavior.
Pure inheritance vs. extension
When studying inheritance, it would seem that the cleanest way to create
an inheritance hierarchy is to take the "pure" approach. That is, only
methods that have been established in the base class or interface are to
be overridden in the derived class, as seen in this diagram:
Shape
draw()
erase()
Circle
Square
Triangle
draw()
draw()
draw()
erase()
erase()
erase()
This can be termed a pure "is-a" relationship because the interface of a
class establishes what it is. Inheritance guarantees that any derived class
Chapter 7: Polymorphism
341
img
will have the interface of the base class and nothing less. If you follow the
above diagram, derived classes will also have no more than the base class
interface.
This can be thought of as pure substitution, because derived class objects
can be perfectly substituted for the base class, and you never need to
know any extra information about the subclasses when you're using them:
Circle, Square,
Talks to Shape
Message
Line, or new type
"Is-a"
of Shape
relationship
That is, the base class can receive any message you can send to the
derived class because the two have exactly the same interface. All you
need to do is upcast from the derived class and never look back to see
what exact type of object you're dealing with. Everything is handled
through polymorphism.
When you see it this way, it seems like a pure "is-a" relationship is the
only sensible way to do things, and any other design indicates muddled
thinking and is by definition broken. This too is a trap. As soon as you
start thinking this way, you'll turn around and discover that extending the
interface (which, unfortunately, the keyword extends seems to
encourage) is the perfect solution to a particular problem. This could be
termed an "is-like-a" relationship because the derived class is like the base
class--it has the same fundamental interface--but it has other features
that require additional methods to implement:
342
Thinking in Java
img
Useful
Assume this
}
void f()
represents a big
void g()
interface
"Is-like-a"
MoreUseful
void f()
void g()
}
void u()
Extending
void v()
the interface
void w()
While this is also a useful and sensible approach (depending on the
situation) it has a drawback. The extended part of the interface in the
derived class is not available from the base class, so once you upcast you
can't call the new methods:
Talks to Useful
Useful part
object
Message
MoreUseful
part
If you're not upcasting in this case, it won't bother you, but often you'll get
into a situation in which you need to rediscover the exact type of the
object so you can access the extended methods of that type. The following
section shows how this is done.
Downcasting and run-time
type identification
Since you lose the specific type information via an upcast (moving up the
inheritance hierarchy), it makes sense that to retrieve the type
information--that is, to move back down the inheritance hierarchy--you
use a downcast. However, you know an upcast is always safe; the base
Chapter 7: Polymorphism
343
img
class cannot have a bigger interface than the derived class, therefore every
message you send through the base class interface is guaranteed to be
accepted. But with a downcast, you don't really know that a shape (for
example) is actually a circle. It could instead be a triangle or square or
some other type.
Useful
Assume this
}
void f()
represents a big
void g()
interface
"Is-like-a"
MoreUseful
void f()
void g()
}
void u()
Extending
void v()
the interface
void w()
To solve this problem there must be some way to guarantee that a
downcast is correct, so you won't accidentally cast to the wrong type and
then send a message that the object can't accept. This would be quite
unsafe.
In some languages (like C++) you must perform a special operation in
order to get a type-safe downcast, but in Java every cast is checked! So
even though it looks like you're just performing an ordinary parenthesized
cast, at run-time this cast is checked to ensure that it is in fact the type
you think it is. If it isn't, you get a ClassCastException. This act of
checking types at run-time is called run-time type identification (RTTI).
The following example demonstrates the behavior of RTTI:
//: c07:RTTI.java
// Downcasting & Run-time Type
// Identification (RTTI).
import java.util.*;
class Useful {
public void f() {}
344
Thinking in Java
img
public void g() {}
}
class MoreUseful extends Useful {
public void f() {}
public void g() {}
public void u() {}
public void v() {}
public void w() {}
}
public class RTTI {
public static void main(String[] args) {
Useful[] x = {
new Useful(),
new MoreUseful()
};
x[0].f();
x[1].g();
// Compile-time: method not found in Useful:
//! x[1].u();
((MoreUseful)x[1]).u(); // Downcast/RTTI
((MoreUseful)x[0]).u(); // Exception thrown
}
} ///:~
As in the diagram, MoreUseful extends the interface of Useful. But
since it's inherited, it can also be upcast to a Useful. You can see this
happening in the initialization of the array x in main( ). Since both
objects in the array are of class Useful, you can send the f( ) and g( )
methods to both, and if you try to call u( ) (which exists only in
MoreUseful) you'll get a compile-time error message.
If you want to access the extended interface of a MoreUseful object, you
can try to downcast. If it's the correct type, it will be successful. Otherwise,
you'll get a ClassCastException. You don't need to write any special
code for this exception, since it indicates a programmer error that could
happen anywhere in a program.
There's more to RTTI than a simple cast. For example, there's a way to see
what type you're dealing with before you try to downcast it. All of Chapter
Chapter 7: Polymorphism
345
img
12 is devoted to the study of different aspects of Java run-time type
identification.
Summary
Polymorphism means "different forms." In object-oriented programming,
you have the same face (the common interface in the base class) and
different forms using that face: the different versions of the dynamically
bound methods.
You've seen in this chapter that it's impossible to understand, or even
create, an example of polymorphism without using data abstraction and
inheritance. Polymorphism is a feature that cannot be viewed in isolation
(like a switch statement can, for example), but instead works only in
concert, as part of a "big picture" of class relationships. People are often
confused by other, non-object-oriented features of Java, like method
overloading, which are sometimes presented as object-oriented. Don't be
fooled: If it isn't late binding, it isn't polymorphism.
To use polymorphism--and thus object-oriented techniques--effectively
in your programs you must expand your view of programming to include
not just members and messages of an individual class, but also the
commonality among classes and their relationships with each other.
Although this requires significant effort, it's a worthy struggle, because
the results are faster program development, better code organization,
extensible programs, and easier code maintenance.
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from .
1.
Add a new method in the base class of Shapes.java that prints a
message, but don't override it in the derived classes. Explain what
happens. Now override it in one of the derived classes but not the
others, and see what happens. Finally, override it in all the derived
classes.
346
Thinking in Java
img
2.
Add a new type of Shape to Shapes.java and verify in main( )
that polymorphism works for your new type as it does in the old
types.
3.
Change Music3.java so that what( ) becomes the root Object
method toString( ). Try printing the Instrument objects using
System.out.println( ) (without any casting).
4.
Add a new type of Instrument to Music3.java and verify that
polymorphism works for your new type.
5.
Modify Music3.java so that it randomly creates Instrument
objects the way Shapes.java does.
6.
Create an inheritance hierarchy of Rodent: Mouse, Gerbil,
Hamster, etc. In the base class, provide methods that are
common to all Rodents, and override these in the derived classes
to perform different behaviors depending on the specific type of
Rodent. Create an array of Rodent, fill it with different specific
types of Rodents, and call your base-class methods to see what
happens.
7.
Modify Exercise 6 so that Rodent is an abstract class. Make the
methods of Rodent abstract whenever possible.
8.
Create a class as abstract without including any abstract
methods, and verify that you cannot create any instances of that
class.
9.
Add class Pickle to Sandwich.java.
10.
Modify Exercise 6 so that it demonstrates the order of
initialization of the base classes and derived classes. Now add
member objects to both the base and derived classes, and show the
order in which their initialization occurs during construction.
11.
Create a 3-level inheritance hierarchy. Each class in the hierarchy
should have a finalize( ) method, and it should properly call the
base-class version of finalize( ). Demonstrate that your hierarchy
works properly.
Chapter 7: Polymorphism
347
img
12.
Create a base class with two methods. In the first method, call the
second method. Inherit a class and override the second method.
Create an object of the derived class, upcast it to the base type, and
call the first method. Explain what happens.
13.
Create a base class with an abstract print( ) method that is
overridden in a derived class. The overridden version of the
method prints the value of an int variable defined in the derived
class. At the point of definition of this variable, give it a nonzero
value. In the base-class constructor, call this method. In main( ),
create an object of the derived type, and then call its print( )
method. Explain the results.
14.
Following the example in Transmogrify.java, create a Starship
class containing an AlertStatus reference that can indicate three
different states. Include methods to change the states.
15.
Create an abstract class with no methods. Derive a class and add
a method. Create a static method that takes a reference to the
base class, downcasts it to the derived class, and calls the method.
In main( ), demonstrate that it works. Now put the abstract
declaration for the method in the base class, thus eliminating the
need for the downcast.
348
Thinking in Java
img
8: Interfaces &
Inner Classes
Interfaces and inner classes provide more sophisticated
ways to organize and control the objects in your system.
C++, for example, does not contain such mechanisms, although the clever
programmer may simulate them. The fact that they exist in Java indicates
that they were considered important enough to provide direct support
through language keywords.
In Chapter 7, you learned about the abstract keyword, which allows you
to create one or more methods in a class that have no definitions--you
provide part of the interface without providing a corresponding
implementation, which is created by inheritors. The interface keyword
produces a completely abstract class, one that provides no
implementation at all. You'll learn that the interface is more than just an
abstract class taken to the extreme, since it allows you to perform a
variation on C++'s "multiple inheritance," by creating a class that can be
upcast to more than one base type.
At first, inner classes look like a simple code-hiding mechanism: you place
classes inside other classes. You'll learn, however, that the inner class
does more than that--it knows about and can communicate with the
surrounding class--and that the kind of code you can write with inner
classes is more elegant and clear, although it is a new concept to most. It
takes some time to become comfortable with design using inner classes.
Interfaces
The interface keyword takes the abstract concept one step further. You
could think of it as a "pure" abstract class. It allows the creator to
establish the form for a class: method names, argument lists, and return
types, but no method bodies. An interface can also contain fields, but
349
img
these are implicitly static and final. An interface provides only a form,
but no implementation.
An interface says: "This is what all classes that implement this particular
interface will look like." Thus, any code that uses a particular interface
knows what methods might be called for that interface, and that's all. So
the interface is used to establish a "protocol" between classes. (Some
object-oriented programming languages have a keyword called protocol
to do the same thing.)
To create an interface, use the interface keyword instead of the class
keyword. Like a class, you can add the public keyword before the
interface keyword (but only if that interface is defined in a file of the
same name) or leave it off to give "friendly" status so that it is only usable
within the same package.
To make a class that conforms to a particular interface (or group of
interfaces) use the implements keyword. You're saying "The
interface is what it looks like but now I'm going to say how it works."
Other than that, it looks like inheritance. The diagram for the instrument
example shows this:
350
Thinking in Java
img
interface Instrument
void play();
String what();
void adjust();
implements
implements
implements
Wind
Percussion
Stringed
void play()
void play()
void play()
String what()
String what()
String what()
void adjust()
void adjust()
void adjust()
extends
extends
Woodwind
Brass
void play()
void play()
String what()
void adjust()
Once you've implemented an interface, that implementation becomes an
ordinary class that can be extended in the regular way.
You can choose to explicitly declare the method declarations in an
interface as public. But they are public even if you don't say it. So
when you implement an interface, the methods from the interface
must be defined as public. Otherwise they would default to "friendly,"
and you'd be reducing the accessibility of a method during inheritance,
which is not allowed by the Java compiler.
You can see this in the modified version of the Instrument example.
Note that every method in the interface is strictly a declaration, which is
the only thing the compiler allows. In addition, none of the methods in
Instrument are declared as public, but they're automatically public
anyway:
//: c08:music5:Music5.java
Chapter 8: Interfaces & Inner Classes
351
img
// Interfaces.
import java.util.*;
interface Instrument {
// Compile-time constant:
int i = 5; // static & final
// Cannot have method definitions:
void play(); // Automatically public
String what();
void adjust();
}
class Wind implements Instrument {
public void play() {
System.out.println("Wind.play()");
}
public String what() { return "Wind"; }
public void adjust() {}
}
class Percussion implements Instrument {
public void play() {
System.out.println("Percussion.play()");
}
public String what() { return "Percussion"; }
public void adjust() {}
}
class Stringed implements Instrument {
public void play() {
System.out.println("Stringed.play()");
}
public String what() { return "Stringed"; }
public void adjust() {}
}
class Brass extends Wind {
public void play() {
System.out.println("Brass.play()");
}
public void adjust() {
352
Thinking in Java
img
System.out.println("Brass.adjust()");
}
}
class Woodwind extends Wind {
public void play() {
System.out.println("Woodwind.play()");
}
public String what() { return "Woodwind"; }
}
public class Music5 {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument i) {
// ...
i.play();
}
static void tuneAll(Instrument[] e) {
for(int i = 0; i < e.length; i++)
tune(e[i]);
}
public static void main(String[] args) {
Instrument[] orchestra = new Instrument[5];
int i = 0;
// Upcasting during addition to the array:
orchestra[i++] = new Wind();
orchestra[i++] = new Percussion();
orchestra[i++] = new Stringed();
orchestra[i++] = new Brass();
orchestra[i++] = new Woodwind();
tuneAll(orchestra);
}
} ///:~
The rest of the code works the same. It doesn't matter if you are upcasting
to a "regular" class called Instrument, an abstract class called
Instrument, or to an interface called Instrument. The behavior is the
same. In fact, you can see in the tune( ) method that there isn't any
evidence about whether Instrument is a "regular" class, an abstract
Chapter 8: Interfaces & Inner Classes
353
img
class, or an interface. This is the intent: Each approach gives the
programmer different control over the way objects are created and used.
"Multiple inheritance" in Java
The interface isn't simply a "more pure" form of abstract class. It has a
higher purpose than that. Because an interface has no implementation
at all--that is, there is no storage associated with an interface--there's
nothing to prevent many interfaces from being combined. This is
valuable because there are times when you need to say "An x is an a and a
b and a c." In C++, this act of combining multiple class interfaces is called
multiple inheritance, and it carries some rather sticky baggage because
each class can have an implementation. In Java, you can perform the
same act, but only one of the classes can have an implementation, so the
problems seen in C++ do not occur with Java when combining multiple
interfaces:
interface 1
Abstract or Concrete
Base Class
...
interface 2
...
interface n
...
Base Class Functions
interface 1
interface 2
interface n
In a derived class, you aren't forced to have a base class that is either an
abstract or "concrete" (one with no abstract methods). If you do inherit
from a non-interface, you can inherit from only one. All the rest of the
base elements must be interfaces. You place all the interface names after
the implements keyword and separate them with commas. You can have
as many interfaces as you want--each one becomes an independent type
that you can upcast to. The following example shows a concrete class
combined with several interfaces to produce a new class:
//: c08:Adventure.java
// Multiple interfaces.
import java.util.*;
354
Thinking in Java
img
interface CanFight {
void fight();
}
interface CanSwim {
void swim();
}
interface CanFly {
void fly();
}
class ActionCharacter {
public void fight() {}
}
class Hero extends ActionCharacter
implements CanFight, CanSwim, CanFly {
public void swim() {}
public void fly() {}
}
public class Adventure {
static void t(CanFight x) { x.fight(); }
static void u(CanSwim x) { x.swim(); }
static void v(CanFly x) { x.fly(); }
static void w(ActionCharacter x) { x.fight(); }
public static void main(String[] args) {
Hero h = new Hero();
t(h); // Treat it as a CanFight
u(h); // Treat it as a CanSwim
v(h); // Treat it as a CanFly
w(h); // Treat it as an ActionCharacter
}
} ///:~
You can see that Hero combines the concrete class ActionCharacter
with the interfaces CanFight, CanSwim, and CanFly. When you
combine a concrete class with interfaces this way, the concrete class must
come first, then the interfaces. (The compiler gives an error otherwise.)
Chapter 8: Interfaces & Inner Classes
355
img
Note that the signature for fight( ) is the same in the interface
CanFight and the class ActionCharacter, and that fight( ) is not
provided with a definition in Hero. The rule for an interface is that you
can inherit from it (as you will see shortly), but then you've got another
interface. If you want to create an object of the new type, it must be a
class with all definitions provided. Even though Hero does not explicitly
provide a definition for fight( ), the definition comes along with
ActionCharacter so it is automatically provided and it's possible to
create objects of Hero.
In class Adventure, you can see that there are four methods that take as
arguments the various interfaces and the concrete class. When a Hero
object is created, it can be passed to any of these methods, which means it
is being upcast to each interface in turn. Because of the way interfaces
are designed in Java, this works without a hitch and without any
particular effort on the part of the programmer.
Keep in mind that the core reason for interfaces is shown in the above
example: to be able to upcast to more than one base type. However, a
second reason for using interfaces is the same as using an abstract base
class: to prevent the client programmer from making an object of this
class and to establish that it is only an interface. This brings up a
question: Should you use an interface or an abstract class? An
interface gives you the benefits of an abstract class and the benefits of
an interface, so if it's possible to create your base class without any
method definitions or member variables you should always prefer
interfaces to abstract classes. In fact, if you know something is going to
be a base class, your first choice should be to make it an interface, and
only if you're forced to have method definitions or member variables
should you change to an abstract class, or if necessary a concrete class.
Name collisions when combining interfaces
You can encounter a small pitfall when implementing multiple interfaces.
In the above example, both CanFight and ActionCharacter have an
identical void fight( ) method. This is no problem because the method is
identical in both cases, but what if it's not? Here's an example:
//: c08:InterfaceCollision.java
356
Thinking in Java
img
interface
I1 { void f(); }
interface
I2 { int f(int i); }
interface
I3 { int f(); }
class C {
public int f() { return 1; } }
class C2 implements I1, I2 {
public void f() {}
public int f(int i) { return 1; } // overloaded
}
class C3 extends C implements I2 {
public int f(int i) { return 1; } // overloaded
}
class C4 extends C implements I3 {
// Identical, no problem:
public int f() { return 1; }
}
// Methods differ only by return type:
//! class C5 extends C implements I1 {}
//! interface I4 extends I1, I3 {} ///:~
The difficulty occurs because overriding, implementation, and
overloading get unpleasantly mixed together, and overloaded functions
cannot differ only by return type. When the last two lines are
uncommented, the error messages say it all:
InterfaceCollision.java:23: f() in C cannot
implement f() in I1; attempting to use
incompatible return type
found
: int
required: void
InterfaceCollision.java:24: interfaces I3 and I1 are
incompatible; both define f
(), but with different return type
Using the same method names in different interfaces that are intended to
be combined generally causes confusion in the readability of the code, as
well. Strive to avoid it.
Chapter 8: Interfaces & Inner Classes
357
img
Extending an interface
with inheritance
You can easily add new method declarations to an interface using
inheritance, and you can also combine several interfaces into a new
interface with inheritance. In both cases you get a new interface, as
seen in this example:
//: c08:HorrorShow.java
// Extending an interface with inheritance.
interface Monster {
void menace();
}
interface DangerousMonster extends Monster {
void destroy();
}
interface Lethal {
void kill();
}
class DragonZilla implements DangerousMonster {
public void menace() {}
public void destroy() {}
}
interface Vampire
extends DangerousMonster, Lethal {
void drinkBlood();
}
class HorrorShow {
static void u(Monster b) { b.menace(); }
static void v(DangerousMonster d) {
d.menace();
d.destroy();
}
public static void main(String[] args) {
358
Thinking in Java
img
DragonZilla if2 = new DragonZilla();
u(if2);
v(if2);
}
} ///:~
DangerousMonster is a simple extension to Monster that produces a
new interface. This is implemented in DragonZilla.
The syntax used in Vampire works only when inheriting interfaces.
Normally, you can use extends with only a single class, but since an
interface can be made from multiple other interfaces, extends can refer
to multiple base interfaces when building a new interface. As you can
see, the interface names are simply separated with commas.
Grouping constants
Because any fields you put into an interface are automatically static and
final, the interface is a convenient tool for creating groups of constant
values, much as you would with an enum in C or C++. For example:
//: c08:Months.java
// Using interfaces to create groups of constants.
package c08;
public interface Months {
int
JANUARY = 1, FEBRUARY = 2, MARCH = 3,
APRIL = 4, MAY = 5, JUNE = 6, JULY = 7,
AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,
NOVEMBER = 11, DECEMBER = 12;
} ///:~
Notice the Java style of using all uppercase letters (with underscores to
separate multiple words in a single identifier) for static finals that have
constant initializers.
The fields in an interface are automatically public, so it's unnecessary
to specify that.
Now you can use the constants from outside the package by importing
c08.* or c08.Months just as you would with any other package, and
Chapter 8: Interfaces & Inner Classes
359
img
referencing the values with expressions like Months.JANUARY. Of
course, what you get is just an int, so there isn't the extra type safety that
C++'s enum has, but this (commonly used) technique is certainly an
improvement over hard-coding numbers into your programs. (That
approach is often referred to as using "magic numbers" and it produces
very difficult-to-maintain code.)
If you do want extra type safety, you can build a class like this1:
//: c08:Month2.java
// A more robust enumeration system.
package c08;
public final class Month2 {
private String name;
private Month2(String nm) { name = nm; }
public String toString() { return name; }
public final static Month2
JAN = new Month2("January"),
FEB = new Month2("February"),
MAR = new Month2("March"),
APR = new Month2("April"),
MAY = new Month2("May"),
JUN = new Month2("June"),
JUL = new Month2("July"),
AUG = new Month2("August"),
SEP = new Month2("September"),
OCT = new Month2("October"),
NOV = new Month2("November"),
DEC = new Month2("December");
public final static Month2[] month =  {
JAN, JAN, FEB, MAR, APR, MAY, JUN,
JUL, AUG, SEP, OCT, NOV, DEC
};
public static void main(String[] args) {
Month2 m = Month2.JAN;
System.out.println(m);
m = Month2.month[12];
1 This approach was inspired by an e-mail from Rich Hoffarth.
360
Thinking in Java
img
System.out.println(m);
System.out.println(m == Month2.DEC);
System.out.println(m.equals(Month2.DEC));
}
} ///:~
The class is called Month2, since there's already a Month in the
standard Java library. It's a final class with a private constructor so no
one can inherit from it or make any instances of it. The only instances are
the final static ones created in the class itself: JAN, FEB, MAR, etc.
These objects are also used in the array month, which lets you choose
months by number instead of by name. (Notice the extra JAN in the array
to provide an offset by one, so that December is month 12.) In main( )
you can see the type safety: m is a Month2 object so it can be assigned
only to a Month2. The previous example Months.java provided only
int values, so an int variable intended to represent a month could
actually be given any integer value, which wasn't very safe.
This approach also allows you to use == or equals( ) interchangeably, as
shown at the end of main( ).
Initializing fields in interfaces
Fields defined in interfaces are automatically static and final. These
cannot be "blank finals," but they can be initialized with nonconstant
expressions. For example:
//: c08:RandVals.java
// Initializing interface fields with
// non-constant initializers.
import java.util.*;
public interface RandVals {
int rint = (int)(Math.random() * 10);
long rlong = (long)(Math.random() * 10);
float rfloat = (float)(Math.random() * 10);
double rdouble = Math.random() * 10;
} ///:~
Chapter 8: Interfaces & Inner Classes
361
img
Since the fields are static, they are initialized when the class is first
loaded, which happens when any of the fields are accessed for the first
time. Here's a simple test:
//: c08:TestRandVals.java
public class TestRandVals {
public static void main(String[] args) {
System.out.println(RandVals.rint);
System.out.println(RandVals.rlong);
System.out.println(RandVals.rfloat);
System.out.println(RandVals.rdouble);
}
} ///:~
The fields, of course, are not part of the interface but instead are stored in
the static storage area for that interface.
Nesting interfaces
2Interfaces
may be nested within classes and within other interfaces. This
reveals a number of very interesting features:
//: c08:NestingInterfaces.java
class A {
interface B {
void f();
}
public class BImp implements B {
public void f() {}
}
private class BImp2 implements B {
public void f() {}
}
public interface C {
void f();
}
2 Thanks to Martin Danner for asking this question during a seminar.
362
Thinking in Java
img
class CImp implements C {
public void f() {}
}
private class CImp2 implements C {
public void f() {}
}
private interface D {
void f();
}
private class DImp implements D {
public void f() {}
}
public class DImp2 implements D {
public void f() {}
}
public D getD() { return new DImp2(); }
private D dRef;
public void receiveD(D d) {
dRef = d;
dRef.f();
}
}
interface E {
interface G {
void f();
}
// Redundant "public":
public interface H {
void f();
}
void g();
// Cannot be private within an interface:
//! private interface I {}
}
public class NestingInterfaces {
public class BImp implements A.B {
public void f() {}
}
class CImp implements A.C {
Chapter 8: Interfaces & Inner Classes
363
img
public void f() {}
}
// Cannot implement a private interface except
// within that interface's defining class:
//! class DImp implements A.D {
//!  public void f() {}
//! }
class EImp implements E {
public void g() {}
}
class EGImp implements E.G {
public void f() {}
}
class EImp2 implements E {
public void g() {}
class EG implements E.G {
public void f() {}
}
}
public static void main(String[] args) {
A a = new A();
// Can't access A.D:
//! A.D ad = a.getD();
// Doesn't return anything but A.D:
//! A.DImp2 di2 = a.getD();
// Cannot access a member of the interface:
//! a.getD().f();
// Only another A can do anything with getD():
A a2 = new A();
a2.receiveD(a.getD());
}
} ///:~
The syntax for nesting an interface within a class is reasonably obvious,
and just like non-nested interfaces these can have public or "friendly"
visibility. You can also see that both public and "friendly" nested
interfaces can be implemented as a public, "friendly," and private
nested classes.
As a new twist, interfaces can also be private as seen in A.D (the same
qualification syntax is used for nested interfaces as for nested classes).
364
Thinking in Java
img
What good is a private nested interface? You might guess that it can only
be implemented as a private nested class as in DImp, but A.DImp2
shows that it can also be implemented as a public class. However,
A.DImp2 can only be used as itself. You are not allowed to mention the
fact that it implements the private interface, so implementing a private
interface is a way to force the definition of the methods in that interface
without adding any type information (that is, without allowing any
upcasting).
The method getD( ) produces a further quandary concerning the private
interface: it's a public method that returns a reference to a private
interface. What can you do with the return value of this method? In
main( ), you can see several attempts to use the return value, all of which
fail. The only thing that works is if the return value is handed to an object
that has permission to use it--in this case, another A, via the received( )
method.
Interface E shows that interfaces can be nested within each other.
However, the rules about interfaces--in particular, that all interface
elements must be public--are strictly enforced here, so an interface
nested within another interface is automatically public and cannot be
made private.
NestingInterfaces shows the various ways that nested interfaces can be
implemented. In particular, notice that when you implement an interface,
you are not required to implement any interfaces nested within. Also,
private interfaces cannot be implemented outside of their defining
classes.
Initially, these features may seem like they are added strictly for syntactic
consistency, but I generally find that once you know about a feature, you
often discover places where it is useful.
Inner classes
It's possible to place a class definition within another class definition. This
is called an inner class. The inner class is a valuable feature because it
allows you to group classes that logically belong together and to control
Chapter 8: Interfaces & Inner Classes
365
img
the visibility of one within the other. However, it's important to
understand that inner classes are distinctly different from composition.
Often, while you're learning about them, the need for inner classes isn't
immediately obvious. At the end of this section, after all of the syntax and
semantics of inner classes have been described, you'll find examples that
should make clear the benefits of inner classes.
You create an inner class just as you'd expect--by placing the class
definition inside a surrounding class:
//: c08:Parcel1.java
// Creating inner classes.
public class Parcel1 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
// Using inner classes looks just like
// using any other class, within Parcel1:
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel1 p = new Parcel1();
p.ship("Tanzania");
}
} ///:~
The inner classes, when used inside ship( ), look just like the use of any
other classes. Here, the only practical difference is that the names are
366
Thinking in Java
img
nested within Parcel1. You'll see in a while that this isn't the only
difference.
More typically, an outer class will have a method that returns a reference
to an inner class, like this:
//: c08:Parcel2.java
// Returning a reference to an inner class.
public class Parcel2 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
public Destination to(String s) {
return new Destination(s);
}
public Contents cont() {
return new Contents();
}
public void ship(String dest) {
Contents c = cont();
Destination d = to(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel2 p = new Parcel2();
p.ship("Tanzania");
Parcel2 q = new Parcel2();
// Defining references to inner classes:
Parcel2.Contents c = q.cont();
Parcel2.Destination d = q.to("Borneo");
}
} ///:~
Chapter 8: Interfaces & Inner Classes
367
img
If you want to make an object of the inner class anywhere except from
within a non-static method of the outer class, you must specify the type
of that object as OuterClassName.InnerClassName, as seen in main( ).
Inner classes and upcasting
So far, inner classes don't seem that dramatic. After all, if it's hiding
you're after, Java already has a perfectly good hiding mechanism--just
allow the class to be "friendly" (visible only within a package) rather than
creating it as an inner class.
However, inner classes really come into their own when you start
upcasting to a base class, and in particular to an interface. (The effect of
producing an interface reference from an object that implements it is
essentially the same as upcasting to a base class.) That's because the inner
class--the implementation of the interface--can then be completely
unseen and unavailable to anyone, which is convenient for hiding the
implementation. All you get back is a reference to the base class or the
interface.
First, the common interfaces will be defined in their own files so they can
be used in all the examples:
//: c08:Destination.java
public interface Destination {
String readLabel();
} ///:~
//: c08:Contents.java
public interface Contents {
int value();
} ///:~
Now Contents and Destination represent interfaces available to the
client programmer. (The interface, remember, automatically makes all
of its members public.)
When you get back a reference to the base class or the interface, it's
possible that you can't even find out the exact type, as shown here:
//: c08:Parcel3.java
// Returning a reference to an inner class.
368
Thinking in Java
img
public class Parcel3 {
private class PContents implements Contents {
private int i = 11;
public int value() { return i; }
}
protected class PDestination
implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
public Destination dest(String s) {
return new PDestination(s);
}
public Contents cont() {
return new PContents();
}
}
class Test {
public static void main(String[] args) {
Parcel3 p = new Parcel3();
Contents c = p.cont();
Destination d = p.dest("Tanzania");
// Illegal -- can't access private class:
//! Parcel3.PContents pc = p.new PContents();
}
} ///:~
Note that since main( ) is in Test, when you want to run this program
you don't execute Parcel3, but instead:
java Test
In the example, main( ) must be in a separate class in order to
demonstrate the privateness of the inner class PContents.
In Parcel3, something new has been added: the inner class PContents
is private so no one but Parcel3 can access it. PDestination is
Chapter 8: Interfaces & Inner Classes
369
img
protected, so no one but Parcel3, classes in the Parcel3 package (since
protected also gives package access--that is, protected is also
"friendly"), and the inheritors of Parcel3 can access PDestination. This
means that the client programmer has restricted knowledge and access to
these members. In fact, you can't even downcast to a private inner class
(or a protected inner class unless you're an inheritor), because you can't
access the name, as you can see in class Test. Thus, the private inner
class provides a way for the class designer to completely prevent any type-
coding dependencies and to completely hide details about
implementation. In addition, extension of an interface is useless from
the client programmer's perspective since the client programmer cannot
access any additional methods that aren't part of the public interface
class. This also provides an opportunity for the Java compiler to generate
more efficient code.
Normal (non-inner) classes cannot be made private or protected--only
public or "friendly."
Inner classes
in methods and scopes
What you've seen so far encompasses the typical use for inner classes. In
general, the code that you'll write and read involving inner classes will be
"plain" inner classes that are simple and easy to understand. However, the
design for inner classes is quite complete and there are a number of other,
more obscure, ways that you can use them if you choose: inner classes can
be created within a method or even an arbitrary scope. There are two
reasons for doing this:
1.
As shown previously, you're implementing an interface of some
kind so that you can create and return a reference.
2.
You're solving a complicated problem and you want to create a
class to aid in your solution, but you don't want it publicly
available.
In the following examples, the previous code will be modified to use:
1.
A class defined within a method
370
Thinking in Java
img
2.
A class defined within a scope inside a method
3.
An anonymous class implementing an interface
4.
An anonymous class extending a class that has a nondefault
constructor
5.
An anonymous class that performs field initialization
6.
An anonymous class that performs construction using instance
initialization (anonymous inner classes cannot have constructors)
Although it's an ordinary class with an implementation, Wrapping is
also being used as a common "interface" to its derived classes:
//: c08:Wrapping.java
public class Wrapping {
private int i;
public Wrapping(int x) { i = x; }
public int value() { return i; }
} ///:~
You'll notice above that Wrapping has a constructor that requires an
argument, to make things a bit more interesting.
The first example shows the creation of an entire class within the scope of
a method (instead of the scope of another class):
//: c08:Parcel4.java
// Nesting a class within a method.
public class Parcel4 {
public Destination dest(String s) {
class PDestination
implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
return new PDestination(s);
}
Chapter 8: Interfaces & Inner Classes
371
img
public static void main(String[] args) {
Parcel4 p = new Parcel4();
Destination d = p.dest("Tanzania");
}
} ///:~
The class PDestination is part of dest( ) rather than being part of
Parcel4. (Also notice that you could use the class identifier
PDestination for an inner class inside each class in the same
subdirectory without a name clash.) Therefore, PDestination cannot be
accessed outside of dest( ). Notice the upcasting that occurs in the return
statement--nothing comes out of dest( ) except a reference to
Destination, the base class. Of course, the fact that the name of the class
PDestination is placed inside dest( ) doesn't mean that PDestination
is not a valid object once dest( ) returns.
The next example shows how you can nest an inner class within any
arbitrary scope:
//: c08:Parcel5.java
// Nesting a class within a scope.
public class Parcel5 {
private void internalTracking(boolean b) {
if(b) {
class TrackingSlip {
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip() { return id; }
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlip();
}
// Can't use it here! Out of scope:
//! TrackingSlip ts = new TrackingSlip("x");
}
public void track() { internalTracking(true); }
public static void main(String[] args) {
Parcel5 p = new Parcel5();
372
Thinking in Java
img
p.track();
}
} ///:~
The class TrackingSlip is nested inside the scope of an if statement.
This does not mean that the class is conditionally created--it gets
compiled along with everything else. However, it's not available outside
the scope in which it is defined. Other than that, it looks just like an
ordinary class.
Anonymous inner classes
The next example looks a little strange:
//: c08:Parcel6.java
// A method that returns an anonymous inner class.
public class Parcel6 {
public Contents cont() {
return new Contents() {
private int i = 11;
public int value() { return i; }
}; // Semicolon required in this case
}
public static void main(String[] args) {
Parcel6 p = new Parcel6();
Contents c = p.cont();
}
} ///:~
The cont( ) method combines the creation of the return value with the
definition of the class that represents that return value! In addition, the
class is anonymous--it has no name. To make matters a bit worse, it looks
like you're starting out to create a Contents object:
return new Contents()
But then, before you get to the semicolon, you say, "But wait, I think I'll
slip in a class definition":
return new Contents() {
private int i = 11;
public int value() { return i; }
Chapter 8: Interfaces & Inner Classes
373
img
};
What this strange syntax means is: "Create an object of an anonymous
class that's inherited from Contents." The reference returned by the new
expression is automatically upcast to a Contents reference. The
anonymous inner-class syntax is a shorthand for:
class MyContents implements Contents {
private int i = 11;
public int value() { return i; }
}
return new MyContents();
In the anonymous inner class, Contents is created using a default
constructor. The following code shows what to do if your base class needs
a constructor with an argument:
//: c08:Parcel7.java
// An anonymous inner class that calls
// the base-class constructor.
public class Parcel7 {
public Wrapping wrap(int x) {
// Base constructor call:
return new Wrapping(x) {
public int value() {
return super.value() * 47;
}
}; // Semicolon required
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Wrapping w = p.wrap(10);
}
} ///:~
That is, you simply pass the appropriate argument to the base-class
constructor, seen here as the x passed in new Wrapping(x). An
anonymous class cannot have a constructor where you would normally
call super( ).
374
Thinking in Java
img
In both of the previous examples, the semicolon doesn't mark the end of
the class body (as it does in C++). Instead, it marks the end of the
expression that happens to contain the anonymous class. Thus, it's
identical to the use of the semicolon everywhere else.
What happens if you need to perform some kind of initialization for an
object of an anonymous inner class? Since it's anonymous, there's no
name to give the constructor--so you can't have a constructor. You can,
however, perform initialization at the point of definition of your fields:
//: c08:Parcel8.java
// An anonymous inner class that performs
// initialization. A briefer version
// of Parcel5.java.
public class Parcel8 {
// Argument must be final to use inside
// anonymous inner class:
public Destination dest(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Destination d = p.dest("Tanzania");
}
} ///:~
If you're defining an anonymous inner class and want to use an object
that's defined outside the anonymous inner class, the compiler requires
that the outside object be final. This is why the argument to dest( ) is
final. If you forget, you'll get a compile-time error message.
As long as you're simply assigning a field, the above approach is fine. But
what if you need to perform some constructor-like activity? With instance
initialization, you can, in effect, create a constructor for an anonymous
inner class:
//: c08:Parcel9.java
Chapter 8: Interfaces & Inner Classes
375
img
// Using "instance initialization" to perform
// construction on an anonymous inner class.
public class Parcel9 {
public Destination
dest(final String dest, final float price) {
return new Destination() {
private int cost;
// Instance initialization for each object:
{
cost = Math.round(price);
if(cost > 100)
System.out.println("Over budget!");
}
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.dest("Tanzania", 101.395F);
}
} ///:~
Inside the instance initializer you can see code that couldn't be executed
as part of a field initializer (that is, the if statement). So in effect, an
instance initializer is the constructor for an anonymous inner class. Of
course, it's limited; you can't overload instance initializers so you can have
only one of these constructors.
The link to the outer class
So far, it appears that inner classes are just a name-hiding and code-
organization scheme, which is helpful but not totally compelling.
However, there's another twist. When you create an inner class, an object
of that inner class has a link to the enclosing object that made it, and so it
can access the members of that enclosing object--without any special
qualifications. In addition, inner classes have access rights to all the
376
Thinking in Java
img
elements in the enclosing class3. The following example demonstrates
this:
//: c08:Sequence.java
// Holds a sequence of Objects.
interface Selector {
boolean end();
Object current();
void next();
}
public class Sequence {
private Object[] obs;
private int next = 0;
public Sequence(int size) {
obs = new Object[size];
}
public void add(Object x) {
if(next < obs.length) {
obs[next] = x;
next++;
}
}
private class SSelector implements Selector {
int i = 0;
public boolean end() {
return i == obs.length;
}
public Object current() {
return obs[i];
}
public void next() {
if(i < obs.length) i++;
}
}
3 This is very different from the design of nested classes in C++, which is simply a name-
hiding mechanism. There is no link to an enclosing object and no implied permissions in
C++.
Chapter 8: Interfaces & Inner Classes
377
img
public Selector getSelector() {
return new SSelector();
}
public static void main(String[] args) {
Sequence s = new Sequence(10);
for(int i = 0; i < 10; i++)
s.add(Integer.toString(i));
Selector sl = s.getSelector();
while(!sl.end()) {
System.out.println(sl.current());
sl.next();
}
}
} ///:~
The Sequence is simply a fixed-sized array of Object with a class
wrapped around it. You call add( ) to add a new Object to the end of the
sequence (if there's room left). To fetch each of the objects in a
Sequence, there's an interface called Selector, which allows you to see
if you're at the end( ), to look at the current( ) Object, and to move to
the next( ) Object in the Sequence. Because Selector is an interface,
many other classes can implement the interface in their own ways, and
many methods can take the interface as an argument, in order to create
generic code.
Here, the SSelector is a private class that provides Selector
functionality. In main( ), you can see the creation of a Sequence,
followed by the addition of a number of String objects. Then, a Selector
is produced with a call to getSelector( ) and this is used to move
through the Sequence and select each item.
At first, the creation of SSelector looks like just another inner class. But
examine it closely. Note that each of the methods end( ), current( ), and
next( ) refer to obs, which is a reference that isn't part of SSelector, but
is instead a private field in the enclosing class. However, the inner class
can access methods and fields from the enclosing class as if they owned
them. This turns out to be very convenient, as you can see in the above
example.
So an inner class has automatic access to the members of the enclosing
class. How can this happen? The inner class must keep a reference to the
378
Thinking in Java
img
particular object of the enclosing class that was responsible for creating it.
Then when you refer to a member of the enclosing class, that (hidden)
reference is used to select that member. Fortunately, the compiler takes
care of all these details for you, but you can also understand now that an
object of an inner class can be created only in association with an object of
the enclosing class. Construction of the inner class object requires the
reference to the object of the enclosing class, and the compiler will
complain if it cannot access that reference. Most of the time this occurs
without any intervention on the part of the programmer.
static inner classes
If you don't need a connection between the inner class object and the
outer class object, then you can make the inner class static. To
understand the meaning of static when applied to inner classes, you must
remember that the object of an ordinary inner class implicitly keeps a
reference to the object of the enclosing class that created it. This is not
true, however, when you say an inner class is static. A static inner class
means:
1.
You don't need an outer-class object in order to create an object of
a static inner class.
2.
You can't access an outer-class object from an object of a static
inner class.
static inner classes are different than non-static inner classes in another
way, as well. Fields and methods in non-static inner classes can only be
at the outer level of a class, so non-static inner classes cannot have static
data, static fields, or static inner classes. However, static inner classes
can have all of these:
//: c08:Parcel10.java
// Static inner classes.
public class Parcel10 {
private static class PContents
implements Contents {
private int i = 11;
public int value() { return i; }
}
Chapter 8: Interfaces & Inner Classes
379
Table of Contents:
  1. Introduction to Objects:The progress of abstraction, An object has an interface
  2. Everything is an Object:You manipulate objects with references, Your first Java program
  3. Controlling Program Flow:Using Java operators, Execution control, true and false
  4. Initialization & Cleanup:Method overloading, Member initialization
  5. Hiding the Implementation:the library unit, Java access specifiers, Interface and implementation
  6. Reusing Classes:Composition syntax, Combining composition and inheritance
  7. Polymorphism:Upcasting revisited, The twist, Designing with inheritance
  8. Interfaces & Inner Classes:Extending an interface with inheritance, Inner class identifiers
  9. Holding Your Objects:Container disadvantage, List functionality, Map functionality
  10. Error Handling with Exceptions:Basic exceptions, Catching an exception
  11. The Java I/O System:The File class, Compression, Object serialization, Tokenizing input
  12. Run-time Type Identification:The need for RTTI, A class method extractor
  13. Creating Windows & Applets:Applet restrictions, Running applets from the command line
  14. Multiple Threads:Responsive user interfaces, Sharing limited resources, Runnable revisited
  15. Distributed Computing:Network programming, Servlets, CORBA, Enterprise JavaBeans
  16. A: Passing & Returning Objects:Aliasing, Making local copies, Cloning objects
  17. B: The Java Native Interface (JNI):Calling a native method, the JNIEnv argument
  18. Java Programming Guidelines:Design, Implementation
  19. Resources:Software, Books, My own list of books
  20. Index