Interfaces and inner classes
provide more sophisticated ways to organize and control the objects in your
system.
C++, for example, does not contain such
mechanisms, although the clever programmer may simulate them. The fact that they
exist in Java indicates that they were considered important enough to provide
direct support through language keywords.
In Chapter 7, you learned about the
abstract keyword, which allows you to create one or more methods in a
class that have no definitions—you provide part of the interface without
providing a corresponding implementation, which is created by inheritors. The
interface keyword produces a completely abstract class, one that provides
no implementation at all. You’ll learn that the interface is more
than just an abstract class taken to the extreme, since it allows you to perform
a variation on C++’s “multiple inheritance,” by creating a
class that can be upcast to more than one base type.
At first, inner classes look like a
simple code-hiding mechanism: you place classes inside other classes.
You’ll learn, however, that the inner class does more than that—it
knows about and can communicate with the surrounding class—and that the
kind of code you can write with inner classes is more elegant and clear,
although it is a new concept to most so it takes some time to become comfortable
with design using inner classes.
The
interface keyword takes the abstract
concept one step further. You could think of it as a “pure”
abstract class. It allows the creator to establish the form for a class:
method names, argument lists, and return types, but no method bodies. An
interface can also contain fields, but these are implicitly
static and final. An
interface provides only a form, but no
implementation.
An interface says: “This is
what all classes that implement this particular interface will look
like.” Thus, any code that uses a particular interface knows what
methods might be called for that interface, and that’s all. So the
interface is used to establish a “protocol” between classes.
(Some object-oriented programming languages have a keyword called
protocol to do the same
thing.)
To create an interface, use the
interface keyword instead of the class keyword. Like a class, you
can add the public keyword before the interface
keyword (but only if that interface is defined in a file of the same
name) or leave it off to give “friendly”
status so that it is only usable within the same package.
To make a class that conforms to a
particular interface (or group of interfaces) use the
implements keyword. You’re saying “The
interface is what it looks like but now I’m going to say how it
works.” Other than that, it looks like inheritance. The diagram for
the instrument example shows this:
Once you’ve implemented an
interface, that implementation becomes an ordinary class that can be
extended in the regular way.
You can choose to explicitly declare the
method declarations in an interface as public. But they are
public even if you don’t say it. So when you implement an
interface, the methods from the interface must be defined as
public. Otherwise they would default to “friendly,” and
you’d be reducing the accessibility of a method during inheritance, which
is not allowed by the Java compiler.
You can see this in the modified version
of the Instrument example. Note that every method in the interface
is strictly a declaration, which is the only thing the compiler allows. In
addition, none of the methods in Instrument are declared as
public, but they’re automatically public
anyway:
//: c08:music5:Music5.java // Interfaces. import java.util.*; interface Instrument { // Compile-time constant: int i = 5; // static & final // Cannot have method definitions: void play(); // Automatically public String what(); void adjust(); } class Wind implements Instrument { public void play() { System.out.println("Wind.play()"); } public String what() { return "Wind"; } public void adjust() {} } class Percussion implements Instrument { public void play() { System.out.println("Percussion.play()"); } public String what() { return "Percussion"; } public void adjust() {} } class Stringed implements Instrument { public void play() { System.out.println("Stringed.play()"); } public String what() { return "Stringed"; } public void adjust() {} } class Brass extends Wind { public void play() { System.out.println("Brass.play()"); } public void adjust() { System.out.println("Brass.adjust()"); } } class Woodwind extends Wind { public void play() { System.out.println("Woodwind.play()"); } public String what() { return "Woodwind"; } } public class Music5 { // Doesn't care about type, so new types // added to the system still work right: static void tune(Instrument i) { // ... i.play(); } static void tuneAll(Instrument[] e) { for(int i = 0; i < e.length; i++) tune(e[i]); } public static void main(String[] args) { Instrument[] orchestra = new Instrument[5]; int i = 0; // Upcasting during addition to the array: orchestra[i++] = new Wind(); orchestra[i++] = new Percussion(); orchestra[i++] = new Stringed(); orchestra[i++] = new Brass(); orchestra[i++] = new Woodwind(); tuneAll(orchestra); } } ///:~
The rest of the code works the same. It
doesn’t matter if you are upcasting to a
“regular” class called Instrument, an abstract class
called Instrument, or to an interface
called Instrument. The behavior is the same. In fact, you can see in the
tune( ) method that there isn’t any evidence about whether
Instrument is a “regular” class, an abstract 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—each one
becomes an independent type that you can upcast to. The following example shows
a concrete class combined with several interfaces to produce a new
class:
//: c08:Adventure.java // Multiple interfaces. import java.util.*; 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, or if necessary a concrete class.
You can encounter a small pitfall when
implementing multiple interfaces. In the above example, both CanFight and
ActionCharacter have an identical void fight( ) method. This
is no problem because the method is identical in both cases, but what if
it’s not? Here’s an example:
//: c08:InterfaceCollision.java interface I1 { void f(); } interface I2 { int f(int i); } interface I3 { int f(); } class C { public int f() { return 1; } } class C2 implements I1, I2 { public void f() {} public int f(int i) { return 1; } // overloaded } class C3 extends C implements I2 { public int f(int i) { return 1; } // overloaded } class C4 extends C implements I3 { // Identical, no problem: public int f() { return 1; } } // Methods differ only by return type: //! class C5 extends C implements I1 {} //! interface I4 extends I1, I3 {} ///:~
The difficulty occurs because overriding,
implementation, and overloading get unpleasantly mixed together, and overloaded
functions cannot differ only by return type. When the last two lines are
uncommented, the error messages say it all:
InterfaceCollision.java:23: f() in C cannot implement f() in I1; attempting to use incompatible return type found : int required: void InterfaceCollision.java:24: interfaces I3 and I1 are incompatible; both define f (), but with different return type
Using the same method names in different
interfaces that are intended to be combined generally causes confusion in the
readability of the code, as well. Strive to avoid
it.
You can easily add new method
declarations to an
interface using
inheritance, and you can also combine several interfaces into a new
interface with inheritance. In both cases you get a new interface,
as seen in this example:
//: c08:HorrorShow.java // Extending an interface with inheritance. interface Monster { void menace(); } interface DangerousMonster extends Monster { void destroy(); } interface Lethal { void kill(); } class DragonZilla implements DangerousMonster { public void menace() {} public void destroy() {} } interface Vampire extends DangerousMonster, Lethal { void drinkBlood(); } class HorrorShow { static void u(Monster b) { b.menace(); } static void v(DangerousMonster d) { d.menace(); d.destroy(); } public static void main(String[] args) { 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:
//: c08:Months.java // Using interfaces to create groups of constants. package c08; public interface Months { int JANUARY = 1, FEBRUARY = 2, MARCH = 3, APRIL = 4, MAY = 5, JUNE = 6, JULY = 7, AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10, NOVEMBER = 11, DECEMBER = 12; } ///:~
Notice the Java style of using all
uppercase letters (with underscores to separate multiple words in a single
identifier) for static finals that have constant
initializers.
The fields in an interface are
automatically public, so it’s unnecessary to specify
that.
Now you can use the constants from
outside the package by importing c08.* or c08.Months just as you
would with any other package, and referencing the values with expressions like
Months.JANUARY. Of course, what you get is just an int, so there
isn’t the extra type safety that C++’s enum has, but this
(commonly used) technique is certainly an improvement over hard-coding numbers
into your programs. (That approach is often referred to as using “magic
numbers” and it produces very difficult-to-maintain
code.)
If you do want extra type safety, you can
build a class like
this[39]:
//: c08:Month2.java // A more robust enumeration system. package c08; public final class Month2 { private String name; private Month2(String nm) { name = nm; } public String toString() { return name; } public final static Month2 JAN = new Month2("January"), FEB = new Month2("February"), MAR = new Month2("March"), APR = new Month2("April"), MAY = new Month2("May"), JUN = new Month2("June"), JUL = new Month2("July"), AUG = new Month2("August"), SEP = new Month2("September"), OCT = new Month2("October"), NOV = new Month2("November"), DEC = new Month2("December"); public final static Month2[] month = { JAN, JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC }; public static void main(String[] args) { Month2 m = Month2.JAN; System.out.println(m); m = Month2.month[12]; System.out.println(m); System.out.println(m == Month2.DEC); System.out.println(m.equals(Month2.DEC)); } } ///:~
The class is called Month2, since
there’s already a Month in the standard Java library. It’s a
final class with a private constructor so no one can inherit from
it or make any instances of it. The only instances are the final static
ones created in the class itself: JAN, FEB, MAR, etc. These
objects are also used in the array month, which lets you choose months by
number instead of by name. (Notice the extra JAN in the array to provide
an offset by one, so that December is month 12.) In main( ) you can
see the type safety: m is a Month2 object
so it can be assigned only to a Month2. The previous example
Months.java provided only int values, so an int variable
intended to represent a month could actually be given any integer value, which
wasn’t very safe.
This approach also allows you to use
== or equals( ) interchangeably, as shown at the end of
main( ).
Fields defined in interfaces are
automatically static and final. These cannot be “blank
finals,” but they can be initialized with nonconstant expressions. For
example:
//: c08:RandVals.java // Initializing interface fields with // non-constant initializers. import java.util.*; public interface RandVals { int rint = (int)(Math.random() * 10); long rlong = (long)(Math.random() * 10); float rfloat = (float)(Math.random() * 10); double rdouble = Math.random() * 10; } ///:~
Since the fields are static, they
are initialized when the class is first loaded, which happens when any of the
fields are accessed for the first time. Here’s a simple
test:
//: c08:TestRandVals.java public class TestRandVals { public static void main(String[] args) { System.out.println(RandVals.rint); System.out.println(RandVals.rlong); System.out.println(RandVals.rfloat); System.out.println(RandVals.rdouble); } } ///:~
The fields, of course, are not part of
the interface but instead are stored in the static storage area for that
interface.
[40]Interfaces
may be nested within classes and within other interfaces. This reveals a number
of very interesting features:
//: c08:NestingInterfaces.java class A { interface B { void f(); } public class BImp implements B { public void f() {} } private class BImp2 implements B { public void f() {} } public interface C { void f(); } class CImp implements C { public void f() {} } private class CImp2 implements C { public void f() {} } private interface D { void f(); } private class DImp implements D { public void f() {} } public class DImp2 implements D { public void f() {} } public D getD() { return new DImp2(); } private D dRef; public void receiveD(D d) { dRef = d; dRef.f(); } } interface E { interface G { void f(); } // Redundant "public": public interface H { void f(); } void g(); // Cannot be private within an interface: //! private interface I {} } public class NestingInterfaces { public class BImp implements A.B { public void f() {} } class CImp implements A.C { public void f() {} } // Cannot implement a private interface except // within that interface's defining class: //! class DImp implements A.D { //! public void f() {} //! } class EImp implements E { public void g() {} } class EGImp implements E.G { public void f() {} } class EImp2 implements E { public void g() {} class EG implements E.G { public void f() {} } } public static void main(String[] args) { A a = new A(); // Can't access A.D: //! A.D ad = a.getD(); // Doesn't return anything but A.D: //! A.DImp2 di2 = a.getD(); // Cannot access a member of the interface: //! a.getD().f(); // Only another A can do anything with getD(): A a2 = new A(); a2.receiveD(a.getD()); } } ///:~
The syntax for nesting an interface
within a class is reasonably obvious, and just like non-nested interfaces these
can have public or “friendly” visibility. You can also see
that both public and “friendly” nested interfaces can be
implemented as a public, “friendly,” and private
nested classes.
As a new twist,
interfaces can also be
private as seen in A.D (the same qualification syntax is used for
nested interfaces as for nested classes). What good is a private nested
interface? You might guess that it can only be implemented as a private
nested class as in DImp, but A.DImp2 shows that it can also be
implemented as a public class. However, A.DImp2 can only be used
as itself. You are not allowed to mention the fact that it implements the
private interface, so implementing a private interface is a way to
force the definition of the methods in that interface without adding any type
information (that is, without allowing any upcasting).
The method getD( ) produces a
further quandary concerning the private interface: it’s a
public method that returns a reference to a private interface.
What can you do with the return value of this method? In main( ),
you can see several attempts to use the return value, all of which fail. The
only thing that works is if the return value is handed to an object that has
permission to use it—in this case, another A, via the
received( ) method.
Interface E shows that interfaces
can be nested within each other. However, the rules about interfaces—in
particular, that all interface elements must be public—are strictly
enforced here, so an interface nested within another interface is automatically
public and cannot be made private.
NestingInterfaces shows the
various ways that nested interfaces can be implemented. In particular, notice
that when you implement an interface, you are not required to implement any
interfaces nested within. Also, private interfaces cannot be implemented
outside of their defining classes.
Initially, these features may seem like they are added strictly for syntactic consistency, but I generally find that once you know about a feature, you often discover places where it is useful."_Toc479507554">
It’s possible to place a class
definition within another class definition. This is called an inner
class. The inner class is a
valuable feature because it allows you to group classes that logically belong
together and to control the visibility of one within the other. However,
it’s important to understand that inner classes are distinctly different
from composition.
Often, while you’re learning about
them, the need for inner classes isn’t immediately obvious. At the end of
this section, after all of the syntax and semantics of inner classes have been
described, you’ll find examples that should make clear the benefits of
inner classes.
You create an inner class just as
you’d expect—by placing the class definition inside a surrounding
class:
//: c08:Parcel1.java // Creating inner classes. public class Parcel1 { class Contents { private int i = 11; public int value() { return i; } } class Destination { private String label; Destination(String whereTo) { label = whereTo; } String readLabel() { return label; } } // Using inner classes looks just like // using any other class, within Parcel1: public void ship(String dest) { Contents c = new Contents(); Destination d = new Destination(dest); System.out.println(d.readLabel()); } public static void main(String[] args) { Parcel1 p = new Parcel1(); p.ship("Tanzania"); } } ///:~
The inner classes, when used inside
ship( ), look just like the use of any other classes. Here, the only
practical difference is that the names are nested within Parcel1.
You’ll see in a while that this isn’t the only
difference.
More typically, an outer class will have
a method that returns a reference to an inner class, like this:
//: c08:Parcel2.java // Returning a reference to an inner class. public class Parcel2 { class Contents { private int i = 11; public int value() { return i; } } class Destination { private String label; Destination(String whereTo) { label = whereTo; } String readLabel() { return label; } } public Destination to(String s) { return new Destination(s); } public Contents cont() { return new Contents(); } public void ship(String dest) { Contents c = cont(); Destination d = to(dest); System.out.println(d.readLabel()); } public static void main(String[] args) { Parcel2 p = new Parcel2(); p.ship("Tanzania"); Parcel2 q = new Parcel2(); // Defining references to inner classes: Parcel2.Contents c = q.cont(); Parcel2.Destination d = q.to("Borneo"); } } ///:~
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
reference from an object that implements it is essentially the same as upcasting
to a base class.) That’s because the inner class—the implementation
of the interface—can then be completely unseen and unavailable to
anyone, which is convenient for hiding the implementation. All you get back is a
reference to the base class or the interface.
First, the common interfaces will be
defined in their own files so they can be used in all the
examples:
//: c08:Destination.java public interface Destination { String readLabel(); } ///:~
//: c08:Contents.java public interface Contents { int value(); } ///:~
Now Contents and
Destination represent interfaces available to the client programmer. (The
interface, remember, automatically makes all of its members
public.)
When you get back a reference to the base
class or the interface, it’s possible that you can’t even
find out the exact type, as shown here:
//: c08:Parcel3.java // Returning a reference to an inner class. public class Parcel3 { private class PContents implements Contents { private int i = 11; public int value() { return i; } } protected class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } public String readLabel() { return label; } } public Destination dest(String s) { return new PDestination(s); } public Contents cont() { return new PContents(); } } class Test { public static void main(String[] args) { Parcel3 p = new Parcel3(); Contents c = p.cont(); Destination d = p.dest("Tanzania"); // Illegal -- can't access private class: //! Parcel3.PContents pc = p.new PContents(); } } ///:~
Note that since main( ) is in
Test, when you want to run this program you don’t execute
Parcel3, but instead:
java Test
In the example, main( ) must
be in a separate class in order to demonstrate the privateness of the inner
class PContents.
In Parcel3, something new has been
added: the inner class PContents is private so no one but
Parcel3 can access it. PDestination is 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.
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:
Although
it’s an ordinary class with an implementation, Wrapping is also
being used as a common “interface” to its derived
classes:
//: c08:Wrapping.java public class Wrapping { private int i; public Wrapping(int x) { i = x; } public int value() { return i; } } ///:~
You’ll notice above that
Wrapping has a constructor that requires an argument, to make things a
bit more interesting.
The first example shows the creation of
an entire class within the scope of a method (instead of the scope of another
class):
//: c08:Parcel4.java // Nesting a class within a method. public class Parcel4 { public Destination dest(String s) { class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } public String readLabel() { return label; } } return new PDestination(s); } public static void main(String[] args) { Parcel4 p = new Parcel4(); Destination d = p.dest("Tanzania"); } } ///:~
The class PDestination is part of
dest( ) rather than being part of Parcel4. (Also notice that
you could use the class identifier PDestination for an inner class inside
each class in the same subdirectory without a name clash.) Therefore,
PDestination cannot be accessed outside of dest( ).
Notice the upcasting that occurs in the return statement—nothing comes
out of dest( ) except a reference to Destination, the base
class. Of course, the fact that the name of the class PDestination is
placed inside dest( ) doesn’t mean that PDestination is
not a valid object once dest( ) returns.
//: c08:Parcel5.java // Nesting a class within a scope. public class Parcel5 { private void internalTracking(boolean b) { if(b) { class TrackingSlip { private String id; TrackingSlip(String s) { id = s; } String getSlip() { return id; } } TrackingSlip ts = new TrackingSlip("slip"); String s = ts.getSlip(); } // Can't use it here! Out of scope: //! TrackingSlip ts = new TrackingSlip("x"); } public void track() { internalTracking(true); } public static void main(String[] args) { Parcel5 p = new Parcel5(); 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:
//: c08:Parcel6.java // A method that returns an anonymous inner class. public class Parcel6 { public Contents cont() { return new Contents() { private int i = 11; public int value() { return i; } }; // Semicolon required in this case } public static void main(String[] args) { Parcel6 p = new Parcel6(); Contents c = p.cont(); } } ///:~
The cont( ) method combines
the creation of the return value with the definition of the class that
represents that return value! In addition, the class is anonymous—it has
no name. To make matters a bit worse, it looks like you’re starting out to
create a Contents object:
return new Contents()
But then, before you get to the
semicolon, you say, “But wait, I think I’ll slip in a class
definition”:
return new Contents() { private int i = 11; public int value() { return i; } };
What this strange syntax means is:
“Create an object of an anonymous class that’s inherited from
Contents.” The reference returned by the new expression is
automatically upcast to a Contents reference. The anonymous inner-class
syntax is a shorthand for:
class MyContents implements Contents { private int i = 11; public int value() { return i; } } return new MyContents();
In the anonymous inner class,
Contents is created using a default constructor. The following code shows
what to do if your base class needs a constructor with an
argument:
//: c08:Parcel7.java // An anonymous inner class that calls // the base-class constructor. public class Parcel7 { public Wrapping wrap(int x) { // Base constructor call: return new Wrapping(x) { public int value() { return super.value() * 47; } }; // Semicolon required } public static void main(String[] args) { Parcel7 p = new Parcel7(); Wrapping w = p.wrap(10); } } ///:~
That is, you simply pass the appropriate
argument to the base-class constructor, seen here as the x passed in
new Wrapping(x). An anonymous class cannot have a constructor where you
would normally call super( ).
In both of the previous examples, the
semicolon doesn’t mark the end of the class body (as it does in C++).
Instead, it marks the end of the expression that happens to contain the
anonymous class. Thus, it’s identical to the use of the semicolon
everywhere else.
What happens if you need to perform some
kind of initialization for an object of an
anonymous
inner class? Since it’s anonymous, there’s no name to give the
constructor—so you can’t have a constructor. You can, however,
perform initialization at the point of definition of your
fields:
//: c08:Parcel8.java // An anonymous inner class that performs // initialization. A briefer version // of Parcel5.java. public class Parcel8 { // Argument must be final to use inside // anonymous inner class: public Destination dest(final String dest) { return new Destination() { private String label = dest; public String readLabel() { return label; } }; } public static void main(String[] args) { Parcel8 p = new Parcel8(); Destination d = p.dest("Tanzania"); } } ///:~
If you’re defining an anonymous
inner class and want to use an object that’s defined outside the anonymous
inner class, the compiler requires that the outside object be final. This
is why the argument to dest( ) is final. If you
forget, you’ll get a compile-time error message.
As long as you’re simply assigning
a field, the above approach is fine. But what if you need to perform some
constructor-like activity? With
instance initialization,
you can, in effect, create a constructor for an anonymous inner
class:
//: c08:Parcel9.java // Using "instance initialization" to perform // construction on an anonymous inner class. public class Parcel9 { public Destination dest(final String dest, final float price) { return new Destination() { private int cost; // Instance initialization for each object: { cost = Math.round(price); if(cost > 100) System.out.println("Over budget!"); } private String label = dest; public String readLabel() { return label; } }; } public static void main(String[] args) { Parcel9 p = new Parcel9(); Destination d = p.dest("Tanzania", 101.395F); } } ///:~
Inside the instance initializer you can
see code that couldn’t be executed as part of a field initializer (that
is, the if statement). So in effect, an instance initializer is the
constructor for an anonymous inner class. Of course, it’s limited; you
can’t overload instance initializers so you can have only one of these
constructors.
So far, it appears that inner classes are
just a name-hiding and code-organization scheme, which is helpful but not
totally compelling. However, there’s another twist. When you create an
inner class, an object of that inner class has a link to the enclosing object
that made it, and so it can access the members of that enclosing
object—without any special qualifications. In addition,
inner
classes have access rights to all the elements in the enclosing
class[41]. The
following example demonstrates this:
//: c08:Sequence.java // Holds a sequence of Objects. interface Selector { boolean end(); Object current(); void next(); } public class Sequence { private Object[] obs; private int next = 0; public Sequence(int size) { obs = new Object[size]; } public void add(Object x) { if(next < obs.length) { obs[next] = x; next++; } } private class SSelector implements Selector { int i = 0; public boolean end() { return i == obs.length; } public Object current() { return obs[i]; } public void next() { if(i < obs.length) i++; } } public Selector getSelector() { return new SSelector(); } public static void main(String[] args) { Sequence s = new Sequence(10); for(int i = 0; i < 10; i++) s.add(Integer.toString(i)); Selector sl = s.getSelector(); while(!sl.end()) { System.out.println(sl.current()); sl.next(); } } } ///:~
The Sequence is simply a
fixed-sized array of Object with a class wrapped around it. You call
add( ) to add a new Object to the end of the sequence (if
there’s room left). To fetch each of the objects in a Sequence,
there’s an interface called Selector, which allows you to see if
you’re at the end( ), to look at the current( )
Object, and to move to the next( ) Object in the
Sequence. Because Selector is an interface, many other
classes can implement the interface in their own ways, and many methods
can take the interface as an argument, in order to create generic
code.
Here, the SSelector is a
private class that provides Selector functionality. In
main( ), you can see the creation of a Sequence, followed by
the addition of a number of String objects. Then, a Selector is
produced with a call to getSelector( ) and this is used to move
through the Sequence and select each item.
At first, the creation of
SSelector looks like just another inner class. But examine it closely.
Note that each of the methods end( ), current( ), and
next( ) refer to obs, which is a reference that isn’t
part of SSelector, but is instead a private field in the enclosing
class. However, the inner class can access methods and fields from the enclosing
class as if they owned them. This turns out to be very convenient, as you can
see in the above example.
So an inner class has automatic access to
the members of the enclosing class. How can this happen? The
inner class must keep a reference to the particular
object of the enclosing class that was responsible for creating it. Then when
you refer to a member of the enclosing class, that (hidden) reference is used to
select that member. Fortunately, the compiler takes care of all these details
for you, but you can also understand now that an object of an inner class can be
created only in association with an object of the enclosing class. Construction
of the inner class object requires the reference to the object of the enclosing
class, and the compiler will complain if it cannot access that reference. Most
of the time this occurs without any intervention on the part of the
programmer.
If you don’t need a connection
between the inner class object and the outer class object, then you can make the
inner class static. To understand the meaning of static when
applied to inner classes, you must remember that the object of an ordinary inner
class implicitly keeps a reference to the object of the enclosing class that
created it. This is not true, however, when you say an inner class is
static. A static inner class means:
static inner
classes are different than non-static inner classes in another way, as
well. Fields and methods in non-static inner classes can only be at the
outer level of a class, so non-static inner classes cannot have
static data, static fields, or static inner classes.
However, static inner classes can have all of these:
//: c08:Parcel10.java // Static inner classes. public class Parcel10 { private static class PContents implements Contents { private int i = 11; public int value() { return i; } } protected static class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } public String readLabel() { return label; } // Static inner classes can contain // other static elements: public static void f() {} static int x = 10; static class AnotherLevel { public static void f() {} static int x = 10; } } 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 references to
Contents and Destination.
As you will see shortly, in an ordinary
(non-static) inner class, the link to the outer class object is achieved
with a special this reference. A static inner class does not have
this special this reference, which makes it analogous to a static
method.
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:
//: c08:IInterface.java // Static inner classes inside interfaces. interface IInterface { static class Inner { int i, j, k; public Inner() {} void f() {} } } ///:~
Earlier in this 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 compiled code you
must carry around. If this is a problem, you can use a static inner class
to hold your test code:
//: c08: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 reference 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 reference to the outer class Sequence by
saying Sequence.this. The resulting reference 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 reference to the other outer class object in the new
expression, like this:
//: c08:Parcel11.java // Creating instances of inner classes. 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 reference to the outer
class object.
[42]It
doesn’t matter how deeply an inner class may be nested—it can
transparently access all of the members of all the classes it is nested within,
as seen here:
//: c08:MultiNestingAccess.java // Nested classes can access all members of all // levels of the classes they are nested within. class MNA { private void f() {} class A { private void g() {} public class B { void h() { g(); f(); } } } } public class MultiNestingAccess { public static void main(String[] args) { MNA mna = new MNA(); MNA.A mnaa = mna.new A(); MNA.A.B mnaab = mnaa.new B(); mnaab.h(); } } ///:~
You can see that in MNA.A.B, the
methods g( ) and f( ) are callable without any
qualification (despite the fact that they are private). This example also
demonstrates the syntax necessary to create objects of multiply-nested inner
classes when you create the objects in a different class. The
“.new” syntax produces the correct scope so you do not have
to qualify the class name in the constructor
call.
Because the inner class constructor must
attach to a reference of the enclosing class object, things are slightly
complicated when you inherit from an inner class. The problem is that the
“secret” reference 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:
//: c08: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
reference to an enclosing object. In addition, you must use the
syntax
enclosingClassReference.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:
//: c08: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. The two inner classes are completely separate entities, each in their own
namespace. However, it’s still possible to explicitly inherit from the
inner class:
//: c08: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 BigEgg2.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 reference 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[43].
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 this fundamental language feature?
Typically, the inner class inherits from
a class or implements an interface, and the code in the inner class
manipulates the outer class object that it was created within. So you could say
that an inner class provides a kind of window into the outer
class.
A question that cuts to the heart of
inner classes is this: if I just need a reference to an interface, why
don’t I just make the outer class implement that interface? The
answer is “If that’s all you need, then that’s how you should
do it.” So what is it that distinguishes an inner class implementing an
interface from an outer class implementing the same interface? The
answer is that you can’t always have the convenience of
interfaces—sometimes you’re working with implementations. So
the most compelling reason for inner classes is:
Each inner class can independently
inherit from an implementation. Thus, the inner class is not limited by whether
the outer class is already inheriting from an
implementation.
Without the ability that inner classes
provide to inherit—in effect—from more than one concrete or
abstract class, some design and programming problems would be
intractable. So one way to look at the inner class is as the completion of the
solution of the multiple-inheritance problem. Interfaces solve part of the
problem, but inner classes effectively allow “multiple implementation
inheritance.” That is, inner classes effectively allow you to inherit from
more than one non-interface.
To see this in more detail, consider a
situation where you have two interfaces that must somehow be implemented within
a class. Because of the flexibility of interfaces, you have two choices: a
single class or an inner class:
//: c08:MultiInterfaces.java // Two ways that a class can // implement multiple interfaces. interface A {} interface B {} class X implements A, B {} class Y implements A { B makeB() { // Anonymous inner class: return new B() {}; } } public class MultiInterfaces { static void takesA(A a) {} static void takesB(B b) {} public static void main(String[] args) { X x = new X(); Y y = new Y(); takesA(x); takesA(y); takesB(x); takesB(y.makeB()); } } ///:~
Of course, this assumes that the
structure of your code makes logical sense either way. However, you’ll
ordinarily have some kind of guidance from the nature of the problem about
whether to use a single class or an inner class. But without any other
constraints, in the above example the approach you take doesn’t really
make much difference from an implementation standpoint. Both of them
work.
However, if you have abstract or
concrete classes instead of interfaces, you are suddenly limited to using
inner classes if your class must somehow implement both of the
others:
//: c08:MultiImplementation.java // With concrete or abstract classes, inner // classes are the only way to produce the effect // of "multiple implementation inheritance." class C {} abstract class D {} class Z extends C { D makeD() { return new D() {}; } } public class MultiImplementation { static void takesC(C c) {} static void takesD(D d) {} public static void main(String[] args) { Z z = new Z(); takesC(z); takesD(z.makeD()); } } ///:~
If you didn’t need to solve the
“multiple implementation inheritance” problem, you could conceivably
code around everything else without the need for inner classes. But with inner
classes you have these additional features:
As an example, if
Sequence.java did not use inner classes, you’d have to say “a
Sequence is a Selector,” and you’d only be able to
have one Selector in existence for a particular Sequence. Also,
you can have a second method, getRSelector( ), that produces a
Selector that moves backward through the sequence. This kind of
flexibility is only available with inner classes.
A closure is a callable object
that retains information from the scope in which it was created. From this
definition, you can see that an inner class is an object-oriented closure,
because it doesn’t just contain each piece of information from the outer
class object (“the scope in which it was created”), but it
automatically holds a reference back to the whole outer class object, where it
has permission to manipulate all the members, even private
ones.
One
of the most compelling arguments made to include some kind of
pointer mechanism in Java was to allow callbacks.
With a callback, some other object is given a piece of information that allows
it to call back into the originating object at some later point. This is a very
powerful concept, as you will see in Chapters 13 and 16. If a callback is
implemented using a pointer, however, you must rely on the programmer to behave
and not misuse the pointer. As you’ve seen by now, Java tends to be more
careful than that, so pointers were not included in the
language.
The closure provided by the inner class
is a perfect solution; more flexible and far safer than a pointer. Here’s
a simple example:
//: c08:Callbacks.java // Using inner classes for callbacks interface Incrementable { void increment(); } class Callee { private int i = 0; private void incr() { i++; System.out.println(i); } private class Closure implements Incrementable { public void increment() { incr(); } } Incrementable getCallbackReference() { return new Closure(); } } class Caller { private Incrementable callbackReference; Caller(Incrementable cbh) { callbackReference = cbh; } void go() { callbackReference.increment(); } } public class Callbacks { public static void main(String[] args) { Callee c = new Callee(); Caller cc = new Caller(c.getCallbackReference()); cc.go(); cc.go(); } } ///:~
Notice that everything except
getCallbackReference( ) in Callee is private. To allow
any connection to the outside world, the interface Incrementable
is essential. Here you can see how interfaces allow for a complete
separation of interface from implementation.
The inner class Closure simply
implements Incrementable to provide a hook back into
Callee—but a safe hook. Whoever gets the Incrementable
reference can, of course, only call increment( ) and has no other
abilities (unlike a pointer, which would allow you to run
wild).
Caller takes an
Incrementable reference in its constructor (although the capturing of the
callback reference could happen at any time) and then, sometime latter, uses the
reference to “call back” into the Callee
class.
The value of the callback is in its
flexibility—you can dynamically decide what functions will be called at
run-time. The benefit of this will become more evident in Chapter 13, where
callbacks are used everywhere to implement graphical user interface (GUI)
functionality.
A more concrete example of the use of
inner classes can be found in 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, in order 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 Swing library is a control framework that elegantly solves
the GUI problem and that heavily uses 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 to perform the control
based on time, so some of the implementation can be included
here:
//: c08:controller:Event.java // The common methods for any control event. package c08.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 following 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 can
replace it with any appropriate container, and in Chapter 9 you’ll
discover other containers that will do the trick without requiring you to write
this extra code:
//: c08:controller:Controller.java // Along with Event, the generic // framework for all control systems: package c08.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” container from Chapter 9 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 reference to null. This is important because if the
garbage collector sees a reference
that’s still in use then it can’t clean up the object. If you think
your references 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.
Consider a
particular implementation of the control framework designed to control
greenhouse
functions[44]. 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. Inner classes allow you to have multiple
derived versions of the same base class, Event, within a single class.
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:
//: c08: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. import c08.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 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 can access those fields
without qualification or special permission. Also, most of the
action( ) methods 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 11 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 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 chapter you should be fully convinced."_Toc479507567">
Interfaces and inner classes are more
sophisticated concepts than what you’ll find in many OOP languages. For
example, there’s nothing like them in C++. Together, they solve the same
problem that C++ attempts to solve with its multiple inheritance (MI) feature.
However, MI in C++ turns out to be rather difficult to use, while Java
interfaces and inner classes are, by comparison, much more
accessible.
Although the features themselves are
reasonably straightforward, the use of these features is a design issue, much
the same as polymorphism. Over time, you’ll become better at recognizing
situations where you should use an interface, or an inner class, or both. But at
this point in this book you should at least be comfortable with the syntax and
semantics. As you see these language features in use you’ll eventually
internalize them.
[39]
This approach was inspired by an e-mail from Rich Hoffarth.
[40]
Thanks to Martin Danner for asking this question during a
seminar.
[41]
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++.
[42]
Thanks again to Martin Danner.
[43]
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.
[44]
For some reason this has always been a pleasing problem for me to solve; it came
from my earlier book C++ Inside & Out, but Java allows a much more
elegant solution.