Inner
classes
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: (See page
97
if you have trouble executing this program.)
//: 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( ).
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 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.
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:
- As
shown previously, you’re implementing an interface of some kind so that
you can create and return a handle.
- 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:
- A
class defined within a method
- A
class defined within a scope inside a method
- An
anonymous
class implementing an
interface
- An
anonymous class extending a class that has a non-default constructor
- An
anonymous class that performs field initialization
- An
anonymous class that performs construction
using instance initialization (anonymous inner classes cannot have constructors)
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.
The
next example shows how you can nest an inner
class within any arbitrary scope:
//: 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:
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.
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, 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.
static
inner classes
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:
- You
don’t need an outer-class object in order to create an object of a
static
inner class.
- You
can’t access an outer-class object from an object of a
static
inner class.
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
class 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.
Referring
to the outer class object
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.
Inheriting
from inner classes
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(); inside
the constructor. This provides the necessary handle and the program will then
compile.
Can
inner classes be overridden?
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.
Inner
class identifiers
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.)
Why
inner classes: control frameworks
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();
} ///:~
The
constructor simply captures the time when you want the
Event
to run, while
ready( )
tells you when it’s time to run it. Of course,
ready( )
could be overridden in a derived class to base the
Event
on something other than time.
action( )
is the method that’s called when the
Event
is
ready( ),
and
description( )
gives textual information about the
Event. The
next file contains the actual control framework that manages and fires events.
The first class is really just a “helper” class whose job is to hold
Event
objects. You could replace it with any appropriate collection, and in Chapter 8
you’ll discover other collections that will do the trick without
requiring you to write this extra code:
//: Controller.java
// Along with Event, the generic
// framework for all control systems:
package c07.controller;
// This is just a way to hold Event objects.
class EventSet {
private Event[] events = new Event[100];
private int index = 0;
private int next = 0;
public void add(Event e) {
if(index >= events.length)
return; // (In real life, throw exception)
events[index++] = e;
}
public Event getNext() {
boolean looped = false;
int start = next;
do {
next = (next + 1) % events.length;
// See if it has looped to the beginning:
if(start == next) looped = true;
// If it loops past start, the list
// is empty:
if((next == (start + 1) % events.length)
&& looped)
return null;
} while(events[next] == null);
return events[next];
}
public void removeCurrent() {
events[next] = null;
}
}
public class Controller {
private EventSet es = new EventSet();
public void addEvent(Event c) { es.add(c); }
public void run() {
Event e;
while((e = es.getNext()) != null) {
if(e.ready()) {
e.action();
System.out.println(e.description());
es.removeCurrent();
}
}
}
} ///:~
EventSet
arbitrarily holds 100
Events.
(If a “real” collection from Chapter 8 is used here you don’t
need to worry about its maximum size, since it will resize itself). The
index
is used to keep track of the next available space, and
next
is used when you’re looking for the next
Event
in the list, to see whether you’ve looped around. This is important
during a call to
getNext( ),
because
Event
objects are removed from the list (using
removeCurrent( ))
once
they’re run, so
getNext( )
will encounter holes in the list as it moves through it.
Note
that
removeCurrent( )
doesn’t just set some flag indicating that the object is no longer in
use. Instead, it sets the handle to
null.
This is important because if the garbage
collector sees a handle that’s still in use then it can’t clean up
the object. If you think your handles might hang around (as they would here),
then it’s a good idea to set them to
null
to give the garbage collector permission to clean them up.
Controller
is where the actual work goes on. It uses an
EventSet
to hold its
Event
objects, and
addEvent( )
allows you to add new events to this list. But the important method is
run( ).
This method loops through the
EventSet,
hunting for an
Event
object that’s
ready( )
to run. For each one it finds
ready( ),
it
calls the
action( )
method, prints out the
description( ),
and then removes the
Event
from the list.
Note
that so far in this design you know nothing about exactly
what
an
Event
does. And this is the crux of the design; how it “separates the things
that change from the things that stay the same.” Or, to use my term, the
“vector
of change” is the different actions of the various kinds of
Event
objects, and you express different actions by creating different
Event
subclasses.
This
is where inner classes come into play. They allow two things:
- To
express the entire implementation of a control-framework application in a
single class, thereby encapsulating everything that’s unique about that
implementation. Inner classes are used to express the many different kinds of
action( )
necessary to solve the problem. In addition, the following example uses private
inner classes so the implementation is completely hidden and can be changed
with impunity.
- Inner
classes keep this implementation from becoming awkward, since you’re able
to easily access any of the members in the outer class. Without this ability
the code might become unpleasant enough that you’d end up seeking an
alternative.
Consider
a particular implementation of the control framework designed to control
greenhouse functions.
[31]
Each action is entirely different: turning lights, water, and thermostats on
and off, ringing bells, and restarting the system. But the control framework is
designed to easily isolate this different code. For each type of action you
inherit a new
Event
inner class, and write the control code inside of
action( ). As
is typical with an application framework, the class
GreenhouseControls
is inherited from
Controller:
//: GreenhouseControls.java
// This produces a specific application of the
// control system, all in a single class. Inner
// classes allow you to encapsulate different
// functionality for each type of event.
package c07.controller;
public class GreenhouseControls
extends Controller {
private boolean light = false;
private boolean water = false;
private String thermostat = "Day";
private class LightOn extends Event {
public LightOn(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here to
// physically turn on the light.
light = true;
}
public String description() {
return "Light is on";
}
}
private class LightOff extends Event {
public LightOff(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here to
// physically turn off the light.
light = false;
}
public String description() {
return "Light is off";
}
}
private class WaterOn extends Event {
public WaterOn(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here
water = true;
}
public String description() {
return "Greenhouse water is on";
}
}
private class WaterOff extends Event {
public WaterOff(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here
water = false;
}
public String description() {
return "Greenhouse water is off";
}
}
private class ThermostatNight extends Event {
public ThermostatNight(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here
thermostat = "Night";
}
public String description() {
return "Thermostat on night setting";
}
}
private class ThermostatDay extends Event {
public ThermostatDay(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here
thermostat = "Day";
}
public String description() {
return "Thermostat on day setting";
}
}
// An example of an action() that inserts a
// new one of itself into the event list:
private int rings;
private class Bell extends Event {
public Bell(long eventTime) {
super(eventTime);
}
public void action() {
// Ring bell every 2 seconds, rings times:
System.out.println("Bing!");
if(--rings > 0)
addEvent(new Bell(
System.currentTimeMillis() + 2000));
}
public String description() {
return "Ring bell";
}
}
private class Restart extends Event {
public Restart(long eventTime) {
super(eventTime);
}
public void action() {
long tm = System.currentTimeMillis();
// Instead of hard-wiring, you could parse
// configuration information from a text
// file here:
rings = 5;
addEvent(new ThermostatNight(tm));
addEvent(new LightOn(tm + 1000));
addEvent(new LightOff(tm + 2000));
addEvent(new WaterOn(tm + 3000));
addEvent(new WaterOff(tm + 8000));
addEvent(new Bell(tm + 9000));
addEvent(new ThermostatDay(tm + 10000));
// Can even add a Restart object!
addEvent(new Restart(tm + 20000));
}
public String description() {
return "Restarting system";
}
}
public static void main(String[] args) {
GreenhouseControls gc =
new GreenhouseControls();
long tm = System.currentTimeMillis();
gc.addEvent(gc.new Restart(tm));
gc.run();
}
} ///:~
Note
that
light,
water,
thermostat,
and
rings
all belong to the outer class
GreenhouseControls,
and yet the inner classes have no problem accessing those fields. Also, most of
the
action( )
methods also involve some sort of hardware control, which would most likely
involve calls to non-Java code.
Most
of the
Event
classes look similar, but
Bell
and
Restart
are special.
Bell
rings, and if it hasn’t yet rung enough times it adds a new
Bell
object to the event list, so it will ring again later. Notice how inner classes
almost
look like multiple inheritance:
Bell
has all the methods of
Event
and it also appears to have all the methods of the outer class
GreenhouseControls. Restart
is responsible for initializing the system, so it adds all the appropriate
events. Of course, a more flexible way to accomplish this is to avoid
hard-coding the events and instead read them from a file. (An exercise in
Chapter 10 asks you to modify this example to do just that.) Since
Restart( )
is just another
Event
object, you can also add a
Restart
object within
Restart.action( )
so that the system regularly restarts itself. And all you need to do in
main( )
is create a
GreenhouseControls
object and add a
Restart
object to get it going.
This
example should move you a long way toward appreciating the value of inner
classes, especially when used within a control framework. However, in the
latter half of Chapter 13 you’ll see how elegantly inner classes are used
to describe the actions of a graphical user interface. By the time you finish
that section you should be fully convinced.
[29]
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++.
[30]
On the other hand, ‘$’ is a meta-character to the Unix shell and so
you’ll sometimes have trouble when listing the
.class
files. This is a bit strange coming from Sun, a Unix-based company. My guess is
that they weren’t considering this issue, but instead thought you’d
naturally focus on the source-code files.
[31]
For some reason this has always been a pleasing problem for me to solve; it
came from
C++
Inside & Out
,
but Java allows a much more elegant solution.