![]() |
James Thornton |
| Internet Business Consultant | Call Toll Free: 1 (800) 409-2501 |
| About James | My MySpace | Internet Marketing | Enron Loophole | Lock Bumping | Contact Me |
|---|
Polymorphism is the third
essential feature of an object-oriented programming language, after data
abstraction and inheritance.
It provides another dimension of
separation of interface from implementation, to decouple what from
how. Polymorphism allows improved code organization and readability as
well as the creation of extensible programs that can be
“grown” not only during the original creation of the project but
also when new features are desired.
Encapsulation creates new data
types by combining characteristics and behaviors. Implementation hiding
separates the interface from the implementation by making the details
private. This sort of mechanical organization makes ready sense to
someone with a procedural programming background. But polymorphism deals with
decoupling in terms of types. In the last chapter,
you saw how inheritance allows the treatment of an object
as its own type or its base type. This ability is critical because it
allows many types (derived from the same base type) to be treated as if they
were one type, and a single piece of code to work on all those different types
equally. The polymorphic method call allows one type to
express its distinction from another, similar type, as long as they’re
both derived from the same base type. This distinction is expressed through
differences in behavior of the methods you can call through the base
class.
In this chapter, you’ll learn
about polymorphism (also called
dynamic
binding or late binding or run-time binding) starting
from the basics, with simple examples that strip away everything but the
polymorphic behavior of the
program.
In Chapter 6 you saw how an object
can be used as its own type or as an object of its base type. Taking an object
handle and treating it as the handle of the base type is called
upcasting because of the way inheritance trees are
drawn with the base class at the top.
You also saw a problem arise, which
is embodied in the following:
//: Music.java
// Inheritance & upcasting
package c07;
class Note {
private int value;
private Note(int val) { value = val; }
public static final Note
middleC = new Note(0),
cSharp = new Note(1),
cFlat = new Note(2);
} // Etc.
class Instrument {
public void play(Note n) {
System.out.println("Instrument.play()");
}
}
// Wind objects are instruments
// because they have the same interface:
class Wind extends Instrument {
// Redefine interface method:
public void play(Note n) {
System.out.println("Wind.play()");
}
}
public class Music {
public static void tune(Instrument i) {
// ...
i.play(Note.middleC);
}
public static void main(String[] args) {
Wind flute = new Wind();
tune(flute); // Upcasting
}
} ///:~
The method
Music.tune( ) accepts an Instrument handle, but also anything
derived from Instrument. In main( ), you can see this
happening as a Wind handle is passed to tune( ), with no cast
necessary. This is acceptable; the interface in Instrument must exist in
Wind, because Wind is inherited from Instrument. Upcasting
from Wind to Instrument may “narrow” that interface,
but it cannot make it anything less than the full interface to
Instrument.
This program might seem strange to
you. Why should anyone intentionally forget the type of an object? This
is what happens when you upcast, and it seems like it could be much more
straightforward if tune( ) simply takes a Wind handle as its
argument. This brings up an essential point: If you did that, you’d need
to write a new tune( ) for every type of Instrument in your
system. Suppose we follow this reasoning and add Stringed and
Brass instruments:
//: Music2.java
// Overloading instead of upcasting
class Note2 {
private int value;
private Note2(int val) { value = val; }
public static final Note2
middleC = new Note2(0),
cSharp = new Note2(1),
cFlat = new Note2(2);
} // Etc.
class Instrument2 {
public void play(Note2 n) {
System.out.println("Instrument2.play()");
}
}
class Wind2 extends Instrument2 {
public void play(Note2 n) {
System.out.println("Wind2.play()");
}
}
class Stringed2 extends Instrument2 {
public void play(Note2 n) {
System.out.println("Stringed2.play()");
}
}
class Brass2 extends Instrument2 {
public void play(Note2 n) {
System.out.println("Brass2.play()");
}
}
public class Music2 {
public static void tune(Wind2 i) {
i.play(Note2.middleC);
}
public static void tune(Stringed2 i) {
i.play(Note2.middleC);
}
public static void tune(Brass2 i) {
i.play(Note2.middleC);
}
public static void main(String[] args) {
Wind2 flute = new Wind2();
Stringed2 violin = new Stringed2();
Brass2 frenchHorn = new Brass2();
tune(flute); // No upcasting
tune(violin);
tune(frenchHorn);
}
} ///:~
This works, but there’s a
major drawback: You must write type-specific methods for each new
Instrument2 class you add. This means more programming in the first
place, but it also means that if you want to add a new method like
tune( ) or a new type of Instrument, you’ve got a lot
of work to do. Add the fact that the compiler won’t give you any error
messages if you forget to overload one of your methods and the whole process of
working with types becomes unmanageable.
Wouldn’t it be much nicer if
you could just write a single method that takes the
base class as its argument, and
not any of the specific derived classes? That is, wouldn’t it be nice if
you could forget that there are
derived classes, and write your
code to talk only to the base class?
That’s exactly what
polymorphism allows you to do. However, most programmers (who come from a
procedural programming background) have a bit of trouble with the way
polymorphism
works.
The difficulty with
Music.java can be seen by running the program. The output is
Wind.play( ). This is clearly the desired output, but it
doesn’t seem to make sense that it would work that way. Look at the
tune( ) method:
public static void tune(Instrument i) {
// ...
i.play(Note.middleC);
}
It receives an Instrument
handle. So how can the compiler possibly know that this Instrument handle
points to a Wind in this case and not a Brass or Stringed?
The compiler can’t. To get a deeper understanding of the issue, it’s
useful to examine the subject of
binding.
Connecting a method call to a
method body is called binding. When binding is performed before the
program is run (by the compiler and linker, if there is one), it’s called
early binding. You might not have heard the term
before because it has never been an option with procedural languages. C
compilers have only one kind of method call, and that’s early
binding.
The confusing part of the above
program revolves around early binding because the compiler cannot know the
correct method to call when it has only an Instrument
handle.
The solution is called late
binding, which means that the
binding occurs at run-time based on the type of object. Late binding is also
called dynamic binding or
run-time binding. When a
language implements late binding, there must be some mechanism to determine the
type of the object at run-time and to call the appropriate method. That is, the
compiler still doesn’t know the object type, but the method-call mechanism
finds out and calls the correct method body. The late-binding mechanism varies
from language to language, but you can imagine that some sort of type
information must be installed in the objects.
All method binding in Java uses
late binding unless a method has been declared
final. This means that you
ordinarily don’t need to make any decisions about whether late binding
will occur – it happens automatically.
Why would you declare a method
final? As noted in the last chapter, it prevents anyone from overriding
that method. Perhaps more importantly, it effectively “turns off”
dynamic binding, or rather it tells the compiler that dynamic binding
isn’t necessary. This allows the compiler to generate more efficient code
for final method
calls.
Once you know that all method
binding in Java happens polymorphically via late binding, you can write your
code to talk to the base-class and know that all the derived-class cases will
work correctly using the same code. Or to put it another way, you “send a
message to an object and let the object figure out the right thing to
do.”
The classic example in OOP is the
“shape” example. This is commonly used
because it is easy to visualize, but unfortunately it can confuse novice
programmers into thinking that OOP is just for graphics programming, which is of
course not the case.
The shape example has a base class
called Shape and various derived types: Circle, Square,
Triangle, etc. The reason the example works so well is that it’s
easy to say “a circle is a type of shape” and be understood.
The inheritance diagram shows the relationships:

The upcast could occur in a
statement as simple as:
Shape s = new Circle();
Here, a Circle object is
created and the resulting handle is immediately assigned to a Shape,
which would seem to be an error (assigning one type to another) and yet
it’s fine because a Circle is a Shape by inheritance.
So the compiler agrees with the statement and doesn’t issue an error
message.
When you call one of the base class
methods (that have been overridden in the derived classes):
s.draw();
Again, you might expect that
Shape’s draw( ) is called because this is, after all, a
Shape handle, so how could the compiler know to do anything else? And yet
the proper Circle.draw( ) is called because of late binding
(polymorphism).
The following example puts it a
slightly different way:
//: Shapes.java
// Polymorphism in Java
class Shape {
void draw() {}
void erase() {}
}
class Circle extends Shape {
void draw() {
System.out.println("Circle.draw()");
}
void erase() {
System.out.println("Circle.erase()");
}
}
class Square extends Shape {
void draw() {
System.out.println("Square.draw()");
}
void erase() {
System.out.println("Square.erase()");
}
}
class Triangle extends Shape {
void draw() {
System.out.println("Triangle.draw()");
}
void erase() {
System.out.println("Triangle.erase()");
}
}
public class Shapes {
public static Shape randShape() {
switch((int)(Math.random() * 3)) {
default: // To quiet the compiler
case 0: return new Circle();
case 1: return new Square();
case 2: return new Triangle();
}
}
public static void main(String[] args) {
Shape[] s = new Shape[9];
// Fill up the array with shapes:
for(int i = 0; i < s.length; i++)
s[i] = randShape();
// Make polymorphic method calls:
for(int i = 0; i < s.length; i++)
s[i].draw();
}
} ///:~
The base class Shape
establishes the common interface to anything inherited from Shape –
that is, all shapes can be drawn and erased. The derived classes override these
definitions to provide unique behavior for each specific type of
shape.
The main class Shapes
contains a static method randShape( ) that produces a handle
to a randomly-selected Shape object each time you call it. Note that the
upcasting happens in each of the return statements, which take a handle
to a Circle, Square, or Triangle and send it out of the
method as the return type, Shape. So whenever you call this method you
never get a chance to see what specific type it is, since you always get back a
plain Shape handle.
main( ) contains an
array of Shape handles filled through calls to randShape( ).
At this point you know you have Shapes, but you don’t know anything
more specific than that (and neither does the compiler). However, when you step
through this array and call draw( ) for each one, the correct
type-specific behavior magically occurs, as you can see from one output
example:
Circle.draw() Triangle.draw() Circle.draw() Circle.draw() Circle.draw() Square.draw() Triangle.draw() Square.draw() Square.draw()
Of course, since the shapes are all
chosen randomly each time, your runs will have different results. The point of
choosing the shapes randomly is to drive home the understanding that the
compiler can have no special knowledge that allows it to make the correct calls
at compile time. All the calls to draw( ) are made through dynamic
binding.
Now let’s return to the
musical instrument example. Because of polymorphism, you can add as many new
types as you want to the system without changing the tune( ) method.
In a well-designed OOP program, most or all of your methods will follow the
model of tune( ) and communicate only with the base-class
interface. Such a program is
extensible because you can add new functionality
by inheriting new data types from the common base class. The methods that
manipulate the base-class interface will not need to be changed at all to
accommodate the new classes.
Consider what happens if you take
the instrument example and add more methods in the base class and a number of
new classes. Here’s the diagram:

All these new classes work
correctly with the old, unchanged tune( ) method. Even if
tune( ) is in a separate file and new methods are added to the
interface of Instrument, tune( ) works correctly without
recompilation. Here is the implementation of the above diagram:
//: Music3.java
// An extensible program
import java.util.*;
class Instrument3 {
public void play() {
System.out.println("Instrument3.play()");
}
public String what() {
return "Instrument3";
}
public void adjust() {}
}
class Wind3 extends Instrument3 {
public void play() {
System.out.println("Wind3.play()");
}
public String what() { return "Wind3"; }
public void adjust() {}
}
class Percussion3 extends Instrument3 {
public void play() {
System.out.println("Percussion3.play()");
}
public String what() { return "Percussion3"; }
public void adjust() {}
}
class Stringed3 extends Instrument3 {
public void play() {
System.out.println("Stringed3.play()");
}
public String what() { return "Stringed3"; }
public void adjust() {}
}
class Brass3 extends Wind3 {
public void play() {
System.out.println("Brass3.play()");
}
public void adjust() {
System.out.println("Brass3.adjust()");
}
}
class Woodwind3 extends Wind3 {
public void play() {
System.out.println("Woodwind3.play()");
}
public String what() { return "Woodwind3"; }
}
public class Music3 {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument3 i) {
// ...
i.play();
}
static void tuneAll(Instrument3[] e) {
for(int i = 0; i < e.length; i++)
tune(e[i]);
}
public static void main(String[] args) {
Instrument3[] orchestra = new Instrument3[5];
int i = 0;
// Upcasting during addition to the array:
orchestra[i++] = new Wind3();
orchestra[i++] = new Percussion3();
orchestra[i++] = new Stringed3();
orchestra[i++] = new Brass3();
orchestra[i++] = new Woodwind3();
tuneAll(orchestra);
}
} ///:~
The new methods are
what( ), which returns a String handle with a description of
the class, and adjust( ), which provides some way to adjust each
instrument.
In main( ), when you
place something inside the Instrument3 array you automatically upcast to
Instrument3.
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.”
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:
//: 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!
}
} ///:~
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 handle
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, 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.
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. 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 this 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 method. 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
X();
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 the derived class is also
abstract and the compiler will force you to qualify that class with the
abstract keyword.
It’s possible to declare 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:

Here’s the orchestra example
modified to use abstract classes and methods:
//: Music4.java
// Abstract classes and methods
import java.util.*;
abstract class Instrument4 {
int i; // storage allocated for each
public abstract void play();
public String what() {
return "Instrument4";
}
public abstract void adjust();
}
class Wind4 extends Instrument4 {
public void play() {
System.out.println("Wind4.play()");
}
public String what() { return "Wind4"; }
public void adjust() {}
}
class Percussion4 extends Instrument4 {
public void play() {
System.out.println("Percussion4.play()");
}
public String what() { return "Percussion4"; }
public void adjust() {}
}
class Stringed4 extends Instrument4 {
public void play() {
System.out.println("Stringed4.play()");
}
public String what() { return "Stringed4"; }
public void adjust() {}
}
class Brass4 extends Wind4 {
public void play() {
System.out.println("Brass4.play()");
}
public void adjust() {
System.out.println("Brass4.adjust()");
}
}
class Woodwind4 extends Wind4 {
public void play() {
System.out.println("Woodwind4.play()");
}
public String what() { return "Woodwind4"; }
}
public class Music4 {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument4 i) {
// ...
i.play();
}
static void tuneAll(Instrument4[] e) {
for(int i = 0; i < e.length; i++)
tune(e[i]);
}
public static void main(String[] args) {
Instrument4[] orchestra = new Instrument4[5];
int i = 0;
// Upcasting during addition to the array:
orchestra[i++] = new Wind4();
orchestra[i++] = new Percussion4();
orchestra[i++] = new Stringed4();
orchestra[i++] = new Brass4();
orchestra[i++] = new Woodwind4();
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.
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 data members of primitive types, but 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.
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 and here’s how it
works.” Other than that, it bears a strong resemblance to
inheritance. The diagram for the instrument example shows this:

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 restricting 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 Instrument5 are declared as
public, but they’re automatically public
anyway:
//: Music5.java
// Interfaces
import java.util.*;
interface Instrument5 {
// Compile-time constant:
int i = 5; // static & final
// Cannot have method definitions:
void play(); // Automatically public
String what();
void adjust();
}
class Wind5 implements Instrument5 {
public void play() {
System.out.println("Wind5.play()");
}
public String what() { return "Wind5"; }
public void adjust() {}
}
class Percussion5 implements Instrument5 {
public void play() {
System.out.println("Percussion5.play()");
}
public String what() { return "Percussion5"; }
public void adjust() {}
}
class Stringed5 implements Instrument5 {
public void play() {
System.out.println("Stringed5.play()");
}
public String what() { return "Stringed5"; }
public void adjust() {}
}
class Brass5 extends Wind5 {
public void play() {
System.out.println("Brass5.play()");
}
public void adjust() {
System.out.println("Brass5.adjust()");
}
}
class Woodwind5 extends Wind5 {
public void play() {
System.out.println("Woodwind5.play()");
}
public String what() { return "Woodwind5"; }
}
public class Music5 {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument5 i) {
// ...
i.play();
}
static void tuneAll(Instrument5[] e) {
for(int i = 0; i < e.length; i++)
tune(e[i]);
}
public static void main(String[] args) {
Instrument5[] orchestra = new Instrument5[5];
int i = 0;
// Upcasting during addition to the array:
orchestra[i++] = new Wind5();
orchestra[i++] = new Percussion5();
orchestra[i++] = new Stringed5();
orchestra[i++] = new Brass5();
orchestra[i++] = new Woodwind5();
tuneAll(orchestra);
}
} ///:~
The rest of the code works the
same. It doesn’t matter if you are upcasting to a
“regular” class called Instrument5, an abstract class
called Instrument5, or to an interface
called Instrument5. The behavior is the same. In fact, you can see in the
tune( ) method that there isn’t any evidence about whether
Instrument5 is a “regular” class, an abstract class or
an interface. This is the intent: Each approach gives the programmer
different control over the way objects are created and
used.
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:

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 and 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:
//: Adventure.java
// Multiple interfaces
import java.util.*;
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 i = new Hero();
t(i); // Treat it as a CanFight
u(i); // Treat it as a CanSwim
v(i); // Treat it as a CanFly
w(i); // 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.)
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.
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:
//: 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) {
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.
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:
//: Months.java
// Using interfaces to create groups of constants
package c07;
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 final primitives that have constant
initializers – that is, for compile-time constants.
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 c07.* or c07.Months just as you
would with any other package, and 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. (This 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
this:[28]
//: Month2.java
// A more robust enumeration system
package c07;
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];
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 too safe.
This approach also allows you to
use == or equals( ) interchangeably, as shown at the end of
main( ).
Fields defined in interfaces are
automatically static and final. These cannot be “blank
finals,” but they can be initialized with non-constant expressions. For
example:
//: 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;
} ///:~
Since the fields are static,
they are initialized when the class is first loaded, upon first access of any of
the fields. Here’s a simple test:
//: 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.
In Java 1.1
it’s possible to place a class definition within another class definition.
This is called an inner class. The
inner class is a useful feature
because it allows you to group classes that logically belong together and to
control 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 an example 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:
//: Parcel1.java
// Creating inner classes
package c07.parcel1;
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);
}
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 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 handle to an inner class, like
this:
//: Parcel2.java
// Returning a handle to an inner class
package c07.parcel2;
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);
}
public static void main(String[] args) {
Parcel2 p = new Parcel2();
p.ship("Tanzania");
Parcel2 q = new Parcel2();
// Defining handles to inner classes:
Parcel2.Contents c = q.cont();
Parcel2.Destination d = q.to("Borneo");
}
} ///:~
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( ).
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 handle from an object that implements it is essentially the same as
upcasting to a base class.) That’s because the inner class can then be
completely unseen and unavailable to anyone, which is convenient for hiding the
implementation. All you get back is a handle to the base class or the
interface, and it’s possible that you can’t even find out the
exact type, as shown here:
//: Parcel3.java
// Returning a handle to an inner class
package c07.parcel3;
abstract class Contents {
abstract public int value();
}
interface Destination {
String readLabel();
}
public class Parcel3 {
private class PContents extends 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 c = p.new PContents();
}
} ///:~
Now Contents and
Destination represent interfaces available to the client programmer. (The
interface, remember, automatically makes all of its members
public.) For convenience, these are placed inside a single file, but
ordinarily Contents and Destination would each be public in
their own files.
In Parcel3, something new
has been added: the inner class PContents is private so no one but
Parcel3 can access it. PDestination is 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.”
Note that Contents
doesn’t need to be an abstract class. You could use an ordinary
class here as well, but the most typical starting point for such a design is an
interface.
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:
This will
all take place within the package innerscopes. First, the common
interfaces from the previous code will be defined in their own files so they can
be used in all the examples:
//: Destination.java
package c07.innerscopes;
interface Destination {
String readLabel();
} ///:~
The point has been made that
Contents could be an abstract class, so here it will be in a more
natural form, as an interface:
//: Contents.java
package c07.innerscopes;
interface Contents {
int value();
} ///:~
Although it’s an ordinary
class with an implementation, Wrapping is also being used as a common
“interface” to its derived classes:
//: Wrapping.java
package c07.innerscopes;
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):
//: Parcel4.java
// Nesting a class within a method
package c07.innerscopes;
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);
}
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 handle to
the base class Destination. 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.
//: Parcel5.java
// Nesting a class within a scope
package c07.innerscopes;
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();
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.
The next example looks a little
strange:
//: Parcel6.java
// A method that returns an anonymous inner class
package c07.innerscopes;
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; }
};
What this strange syntax means is
“create an object of an anonymous class that’s inherited from
Contents.” The handle returned by the new expression is
automatically upcast to a Contents handle. The anonymous inner class
syntax is a shorthand for:
class MyContents extends 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:
//: Parcel7.java
// An anonymous inner class that calls the
// base-class constructor
package c07.innerscopes;
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( ).
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:
//: Parcel8.java
// An anonymous inner class that performs
// initialization. A briefer version
// of Parcel5.java.
package c07.innerscopes;
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 Java 1.1
instance initialization,
you can, in effect, create a constructor for an anonymous inner
class:
//: Parcel9.java
// Using "instance initialization" to perform
// construction on an anonymous inner class
package c07.innerscopes;
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.
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, objects of that inner class have a link to the enclosing
object that made them, and so they can access the members of that enclosing
object – without any special qualifications. In addition,
inner
classes have access rights to all the elements in the enclosing
class.[29]
The following example demonstrates this:
//: Sequence.java
// Holds a sequence of Objects
interface Selector {
boolean end();
Object current();
void next();
}
public class Sequence {
private Object[] o;
private int next = 0;
public Sequence(int size) {
o = new Object[size];
}
public void add(Object x) {
if(next < o.length) {
o[next] = x;
next++;
}
}
private class SSelector implements Selector {
int i = 0;
public boolean end() {
return i == o.length;
}
public Object current() {
return o[i];
}
public void next() {
if(i < o.length) i++;
}
}
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((String)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 o, which is a handle 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 access to the
members of the enclosing class. How can this happen? The
inner class must keep a reference to the 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. The process
of construction requires the initialization of the handle to the object of the
enclosing class, and the compiler will complain if it cannot access the handle.
Most of the time this occurs without any intervention on the part of the
programmer.
To understand the meaning of
static when applied to
inner classes, you must remember that the object of the inner class implicitly
keeps a handle 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:
There are some
restrictions: static members can be at only the outer level of a class,
so inner classes cannot have static data or static inner
classes.
If
you don’t need to create an object of the outer class in order to create
an object of the inner class, you can make everything static. For this to
work, you must also make the inner classes static:
//: Parcel10.java
// Static inner classes
package c07.parcel10;
abstract class Contents {
abstract public int value();
}
interface Destination {
String readLabel();
}
public class Parcel10 {
private static class PContents
extends Contents {
private int i = 11;
public int value() { return i; }
}
protected static class PDestination
implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
public static Destination dest(String s) {
return new PDestination(s);
}
public static Contents cont() {
return new PContents();
}
public static void main(String[] args) {
Contents c = cont();
Destination d = dest("Tanzania");
}
} ///:~
In main( ), no object
of Parcel10 is necessary; instead you use the normal syntax for selecting
a static member to call the methods that return handles to
Contents and Destination.
Normally you can't put any code
inside an interface, but a static inner class can be part of an
interface. Since the class is static it doesn't violate the rules
for interfaces – the static inner class is only placed inside the
namespace of the interface:
//: IInterface.java
// Static inner classes inside interfaces
interface IInterface {
static class Inner {
int i, j, k;
public Inner() {}
void f() {}
}
} ///:~
Earlier in the book I suggested
putting a main( ) in every class to act as a
test bed for that class. One drawback to this is the
amount of extra code you must carry around. If this is a problem, you can use a
static inner class to hold your test code:
//: TestBed.java
// Putting test code in a static inner class
class TestBed {
TestBed() {}
void f() { System.out.println("f()"); }
public static class Tester {
public static void main(String[] args) {
TestBed t = new TestBed();
t.f();
}
}
} ///:~
This generates a separate class
called TestBed$Tester (to run the program you say java
TestBed$Tester). You can use this class for testing, but you don't need to
include it in your shipping
product.
If you need to produce the handle
to the outer class object, you name the outer class followed by a dot and
this. For example, in the class Sequence.SSelector, any of its
methods can produce the stored handle to the outer class Sequence by
saying Sequence.this. The resulting handle is automatically the correct
type. (This is known and checked at compile time, so there is no run-time
overhead.)
Sometimes you want to tell some
other object to create an object of one of its inner classes. To do this you
must provide a handle to the other outer class object in the new
expression, like this:
//: Parcel11.java
// Creating inner classes
package c07.parcel11;
public class Parcel11 {
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 static void main(String[] args) {
Parcel11 p = new Parcel11();
// Must use instance of outer class
// to create an instances of the inner class:
Parcel11.Contents c = p.new Contents();
Parcel11.Destination d =
p.new Destination("Tanzania");
}
} ///:~
To create an object of the inner
class directly, you don’t follow the same form and refer to the outer
class name Parcel11 as you might expect, but instead you must use an
object of the outer class to make an object of the inner
class:
Parcel11.Contents c = p.new Contents();
Thus, it’s not possible to
create an object of the inner class unless you already have an object of the
outer class. This is because the object of the inner class is quietly connected
to the object of the outer class that it was made from. However, if you make a
static inner class, then it doesn’t need a handle to the outer
class object.
Because the inner class constructor
must attach to a handle of the enclosing class object, things are slightly
complicated when you inherit from an inner class. The problem is that the
“secret” handle to the enclosing class object must be
initialized, and yet in the derived class there’s no longer a default
object to attach to. The answer is to use a syntax provided to make the
association explicit:
//: InheritInner.java
// Inheriting an inner class
class WithInner {
class Inner {}
}
public class InheritInner
extends WithInner.Inner {
//! InheritInner() {} // Won't compile
InheritInner(WithInner wi) {
wi.super();
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
} ///:~
You can see that
InheritInner is extending only the inner class, not the outer one. But
when it comes time to create a constructor, the default one is no good and you
can’t just pass a handle to an enclosing object. In addition, you must use
the syntax
enclosingClassHandle.super();
What happens when you create an
inner class, then inherit from the enclosing class and redefine the inner class?
That is, is it possible to override an inner class? This seems like it would be
a powerful concept, but
“overriding”
an inner class as if it were another method of the outer class doesn’t
really do anything:
//: BigEgg.java
// An inner class cannot be overriden
// like a method
class Egg {
protected class Yolk {
public Yolk() {
System.out.println("Egg.Yolk()");
}
}
private Yolk y;
public Egg() {
System.out.println("New Egg()");
y = new Yolk();
}
}
public class BigEgg extends Egg {
public class Yolk {
public Yolk() {
System.out.println("BigEgg.Yolk()");
}
}
public static void main(String[] args) {
new BigEgg();
}
} ///:~
The default constructor is
synthesized automatically by the compiler, and this calls the base-class default
constructor. You might think that since a BigEgg is being created, the
“overridden” version of Yolk would be used, but this is not
the case. The output is:
New Egg() Egg.Yolk()
This example simply shows that
there isn’t any extra inner class magic going on when you inherit from the
outer class. However, it’s still possible to explicitly inherit from the
inner class:
//: BigEgg2.java
// Proper inheritance of an inner class
class Egg2 {
protected class Yolk {
public Yolk() {
System.out.println("Egg2.Yolk()");
}
public void f() {
System.out.println("Egg2.Yolk.f()");
}
}
private Yolk y = new Yolk();
public Egg2() {
System.out.println("New Egg2()");
}
public void insertYolk(Yolk yy) { y = yy; }
public void g() { y.f(); }
}
public class BigEgg2 extends Egg2 {
public class Yolk extends Egg2.Yolk {
public Yolk() {
System.out.println("BigEgg2.Yolk()");
}
public void f() {
System.out.println("BigEgg2.Yolk.f()");
}
}
public BigEgg2() { insertYolk(new Yolk()); }
public static void main(String[] args) {
Egg2 e2 = new BigEgg2();
e2.g();
}
} ///:~
Now BiggEgg2.Yolk explicitly
extends Egg2.Yolk and overrides its methods. The method
insertYolk( ) allows BigEgg2 to upcast one of its own Yolk
objects into the y handle in Egg2, so when g( )
calls y.f( ) the overridden version of f( ) is used. The
output is:
Egg2.Yolk() New Egg2() Egg2.Yolk() BigEgg2.Yolk() BigEgg2.Yolk.f()
The second call to
Egg2.Yolk( ) is the base-class constructor call of the
BigEgg2.Yolk constructor. You can see that the overridden version of
f( ) is used when g( ) is
called.
Since every class produces a
.class file that holds all the information about how to create objects of
this type (this information produces a meta-class called the Class
object), you might guess that
inner classes must also produce
.class files to contain the information for their Class
objects. The names of these files/classes have a strict formula: the name of the
enclosing class, followed by a ‘$’, followed by the name of
the inner class. For example, the .class files created by
InheritInner.java include:
InheritInner.class WithInner$Inner.class WithInner.class
If inner classes are anonymous, the
compiler simply starts generating numbers as inner class identifiers. If inner
classes are nested within inner classes, their names are simply appended after a
‘$’ and the outer class identifier(s).
Although this scheme of generating
internal names is simple and straightforward, it’s also robust and handles
most
situations.[30]
Since it is the standard naming scheme for Java, the generated files are
automatically platform-independent. (Note that the Java compiler is changing
your inner classes in all sorts of other ways in order to make them
work.)
At this point you’ve seen a
lot of syntax and semantics describing the way inner classes work, but this
doesn’t answer the question of why they exist. Why did Sun go to so much
trouble to add such a fundamental language feature in Java
1.1? The answer is something that I will refer to here as
a
control
framework.
An
application framework is a class or a set of
classes that’s designed to solve a particular type of problem. To apply an
application framework, you inherit from one or more classes and override some of
the methods. The code you write in the overridden methods customizes the general
solution provided by that application framework to solve your specific problem.
The control framework is a particular type of application framework
dominated by the need to respond to events; a system that primarily responds to
events is called an event-driven system.
One of the most important problems in application programming is the
graphical
user interface (GUI), which is almost entirely event-driven. As you will see in
Chapter 13, the Java 1.1 AWT is a control framework that elegantly solves the
GUI problem using inner classes.
To see how inner classes allow the
simple creation and use of control frameworks, consider a control framework
whose job is to execute events whenever those events are “ready.”
Although “ready” could mean anything, in this case the default will
be based on clock time. What follows is a control framework that contains no
specific information about what it’s controlling. First, here is the
interface that describes any control event. It’s an abstract class
instead of an actual interface because the default behavior is control
based on time, so some of the implementation can be included
here:
//: Event.java
// The common methods for any control event
package c07.controller;
abstract public class Event {
private long evtTime;
public Event(long eventTime) {
evtTime = eventTime;
}
public boolean ready() {
return System.currentTimeMillis() >= evtTime;
}
abstract public void action();
abstract public String description();
}