| Thinking in Java source ref: work2.html |
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 definitionsyou 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. Youll 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. Youll learn, however, that the inner class does more than
thatit knows about and can communicate with the surrounding classand that the
kind of code you can write with inner classes is more elegant and clear, although it is a
new concept to most. It takes some time to become comfortable with design using inner
classes. .
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 thats 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 package access, 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, which says, The interface
is what it looks like, but now Im going to say how it works. Other than
that, it looks like inheritance. The diagram for the instrument example shows this:

You can see from the Woodwind and Brass classes that once youve
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 dont say it. So when you implement an interface,
the methods from the interface must be defined as public. Otherwise, they
would default to package access, and youd 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 theyre automatically public anyway:
//: c08:music5:Music5.java
// Interfaces.
package c08.music5;
import com.bruceeckel.simpletest.*;
import c07.music.Note;
interface Instrument {
// Compile-time constant:
int I = 5; // static & final
// Cannot have method definitions:
void play(Note n); // Automatically public
String what();
void adjust();
}
class Wind implements Instrument {
public void play(Note n) {
System.out.println("Wind.play() " + n);
}
public String what() { return "Wind"; }
public void adjust() {}
}
class Percussion implements Instrument {
public void play(Note n) {
System.out.println("Percussion.play() " + n);
}
public String what() { return "Percussion"; }
public void adjust() {}
}
class Stringed implements Instrument {
public void play(Note n) {
System.out.println("Stringed.play() " + n);
}
public String what() { return "Stringed"; }
public void adjust() {}
}
class Brass extends Wind {
public void play(Note n) {
System.out.println("Brass.play() " + n);
}
public void adjust() {
System.out.println("Brass.adjust()");
}
}
class Woodwind extends Wind {
public void play(Note n) {
System.out.println("Woodwind.play() " + n);
}
public String what() { return "Woodwind"; }
}
public class Music5 {
private static Test monitor = new Test();
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
static void tuneAll(Instrument[] e) {
for(int i = 0; i < e.length; i++)
tune(e[i]);
}
public static void main(String[] args) {
// Upcasting during addition to the array:
Instrument[] orchestra = {
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchestra);
monitor.expect(new String[] {
"Wind.play() Middle C",
"Percussion.play() Middle C",
"Stringed.play() Middle C",
"Brass.play() Middle C",
"Woodwind.play() Middle C"
});
}
} ///:~
The rest of the code works the same. It doesnt 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 isnt 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 isnt simply a more pure form of abstract
class. It has a higher purpose than that. Because an interface has no
implementation at allthat is, there is no storage associated with an interfacetheres
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 arent 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.
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 {
public static void t(CanFight x) { x.fight(); }
public static void u(CanSwim x) { x.swim(); }
public static void v(CanFly x) { x.fly(); }
public static void w(ActionCharacter x) { x.fight(); }
public static void main(String[] args) {
Hero h = new Hero();
t(h); // Treat it as a CanFight
u(h); // Treat it as a CanSwim
v(h); // Treat it as a CanFly
w(h); // Treat it as an ActionCharacter
}
} ///:~
You can see that Hero combines the concrete class ActionCharacter with
the interfaces CanFight, CanSwim, and CanFly. When you combine a
concrete class with interfaces this way, the concrete class must come first, then the
interfaces. (The compiler gives an error otherwise.) .
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 youve 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 its 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 any
particular effort on the part of the programmer. .
Keep in mind that the core reason for interfaces is shown in the preceding 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 its 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 youre 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 preceding example, both CanFight and ActionCharacter
have an identical void fight( ) method. This is not a problem, because the
method is identical in both cases. But what if it isnt? Heres 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 methods 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 by 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 VeryBadVampire implements Vampire {
public void menace() {}
public void destroy() {}
public void kill() {}
public void drinkBlood() {}
}
public class HorrorShow {
static void u(Monster b) { b.menace(); }
static void v(DangerousMonster d) {
d.menace();
d.destroy();
}
static void w(Lethal l) { l.kill(); }
public static void main(String[] args) {
DangerousMonster barney = new DragonZilla();
u(barney);
v(barney);
Vampire vlad = new VeryBadVampire();
u(vlad);
v(vlad);
w(vlad);
}
} ///:~
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 its
unnecessary to specify that. .
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 isnt
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:[33]
//: c08:Month.java
// A more robust enumeration system.
package c08;
import com.bruceeckel.simpletest.*;
public final class Month {
private static Test monitor = new Test();
private String name;
private Month(String nm) { name = nm; }
public String toString() { return name; }
public static final Month
JAN = new Month("January"),
FEB = new Month("February"),
MAR = new Month("March"),
APR = new Month("April"),
MAY = new Month("May"),
JUN = new Month("June"),
JUL = new Month("July"),
AUG = new Month("August"),
SEP = new Month("September"),
OCT = new Month("October"),
NOV = new Month("November"),
DEC = new Month("December");
public static final Month[] month = {
JAN, FEB, MAR, APR, MAY, JUN,
JUL, AUG, SEP, OCT, NOV, DEC
};
public static final Month number(int ord) {
return month[ord - 1];
}
public static void main(String[] args) {
Month m = Month.JAN;
System.out.println(m);
m = Month.number(12);
System.out.println(m);
System.out.println(m == Month.DEC);
System.out.println(m.equals(Month.DEC));
System.out.println(Month.month[3]);
monitor.expect(new String[] {
"January",
"December",
"true",
"true",
"April"
});
}
} ///:~
Month is 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 iterate through an array of Month2
objects. The number( ) method allows you to select a Month by giving
its corresponding month number. In main( ) you can see the type
safety; m is a Month object so it can be assigned only to a Month.
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
wasnt very safe. .
This approach also allows you to use == or equals( )
interchangeably, as shown at the end of main( ). This works because there can
be only one instance of each value of Month. In Chapter 11 youll learn about
another way to set up classes so the objects can be compared to each other. .
Theres also a month field in java.util.Calendar. .
Apaches Jakarta Commons project contains tools to create enumerations similar to
whats shown in the preceding example, but with less effort. See http://jakarta.apache.org/commons,
under lang, in the package org.apache.commons.lang.enum. This project
also has many other potentially useful libraries. .
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 {
Random rand = new Random();
int randomInt = rand.nextInt(10);
long randomLong = rand.nextLong() * 10;
float randomFloat = rand.nextLong() * 10;
double randomDouble = rand.nextDouble() * 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. Heres
a simple test: .
//: c08:TestRandVals.java
import com.bruceeckel.simpletest.*;
public class TestRandVals {
private static Test monitor = new Test();
public static void main(String[] args) {
System.out.println(RandVals.randomInt);
System.out.println(RandVals.randomLong);
System.out.println(RandVals.randomFloat);
System.out.println(RandVals.randomDouble);
monitor.expect(new String[] {
"%% -?\\d+",
"%% -?\\d+",
"%% -?\\d\\.\\d+E?-?\\d+",
"%% -?\\d\\.\\d+E?-?\\d+"
});
}
} ///:~
The fields, of course, are not part of the interface but instead are stored in the
static storage area for that interface. .
Interfaces may be nested within classes
and within other interfaces.[34] This reveals
a number of very interesting features:
//: c08:nesting:NestingInterfaces.java
package c08.nesting;
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 package-access visibility. You can
also see that both public and package-access nested interfaces can be implemented
as public, package-access, 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 inner 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: Its 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
itin this case, another A, via the receiveD( ) method. .
Interface E shows that interfaces can be nested within each other. However, the
rules about interfacesin particular, that all interface elements must be publicare
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. .
Its 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, its important to understand that inner classes are
distinctly different from composition. .
While youre learning about them,
the need for inner classes isnt always obvious. At the end of this section, after
all of the syntax and semantics of inner classes have been described, youll find
examples that should begin to make clear the benefits of inner classes. .
You create an inner class just as youd expectby 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.
Youll see in a while that this isnt 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 dont seem that dramatic. After all, if its hiding youre after,
Java already has a perfectly good hiding mechanismjust give the class package access
(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.)
Thats because the inner classthe implementation of the interfacecan
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, its
possible that you cant even find out the exact type, as shown here:
//: c08:TestParcel.java
// Returning a reference to an inner class.
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();
}
}
public class TestParcel {
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();
}
} ///:~
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 same package (since protected also
gives package access), 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 cant even downcast to a private inner class (or a protected
inner class unless youre an inheritor), because you cant access the name, as
you can see in class TestParcel. 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 programmers perspective since the client programmer
cannot access any additional methods that arent part of the public interface.
This also provides an opportunity for the Java compiler to generate more efficient code. .
Normal (non-inner) classes cannot be made private or protected; they may
only be given public or package access. .
What
youve seen so far encompasses the typical use for inner classes. In general, the
code that youll 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: .
In the following examples, the previous code will be modified to use: .
Although its 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; }
} ///:~
Youll notice 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). This is called a local inner 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 statementnothing 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( ) doesnt mean that PDestination
is not a valid object once dest( ) returns. .
The next example shows how you can nest an inner class within any arbitrary scope:
//: c08:Parcel5.java
// Nesting a class within a scope.
public class Parcel5 {
private void internalTracking(boolean b) {
if(b) {
class TrackingSlip {
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip() { return id; }
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlip();
}
// Can't use it here! Out of scope:
//! TrackingSlip ts = new TrackingSlip("x");
}
public void track() { internalTracking(true); }
public static void main(String[] args) {
Parcel5 p = new Parcel5();
p.track();
}
} ///:~
The class TrackingSlip is nested inside the scope of an if statement.
This does not mean that the class is conditionally createdit gets compiled
along with everything else. However, its 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 youre starting
out to create a Contents object: .
return new Contents()
But then, before you get to the semicolon, you say, But wait, I think Ill
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
thats 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 by 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) { // Pass constructor argument.
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).
The semicolon at the end of the anonymous inner class doesnt 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, its identical to the use of the semicolon
everywhere else. .
You can also perform initialization when you define fields in an anonymous class:
//: c08:Parcel8.java
// An anonymous inner class that performs
// initialization. A briefer version of Parcel4.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 youre defining an anonymous inner class and want to use an object thats
defined outside the anonymous inner class, the compiler requires that the argument
reference be final, like the argument to dest( ). If you forget,
youll get a compile-time error message. .
As long as youre simply assigning a field, the approach in this example is fine.
But what if you need to perform some constructor-like activity? You cant have a
named constructor in an anonymous class (since theres no name!), but with instance initialization, you can, in
effect, create a constructor for an anonymous inner class, like this: .
//: c08:AnonymousConstructor.java
// Creating a constructor for an anonymous inner class.
import com.bruceeckel.simpletest.*;
abstract class Base {
public Base(int i) {
System.out.println("Base constructor, i = " + i);
}
public abstract void f();
}
public class AnonymousConstructor {
private static Test monitor = new Test();
public static Base getBase(int i) {
return new Base(i) {
{
System.out.println("Inside instance initializer");
}
public void f() {
System.out.println("In anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();
monitor.expect(new String[] {
"Base constructor, i = 47",
"Inside instance initializer",
"In anonymous f()"
});
}
} ///:~
In this case, the variable i did not have to be final. While i is
passed to the base constructor of the anonymous class, it is never directly used inside
the anonymous class. .
Heres the parcel theme with instance initialization. Note that the
arguments to dest( ) must be final since they are used within the anonymous
class:
//: c08:Parcel9.java
// Using "instance initialization" to perform
// construction on an anonymous inner class.
import com.bruceeckel.simpletest.*;
public class Parcel9 {
private static Test monitor = new Test();
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);
monitor.expect(new String[] {
"Over budget!"
});
}
} ///:~
Inside the instance initializer you can see code that couldnt 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, its limited;
you cant 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, theres 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 objectwithout
any special qualifications. In addition, inner classes have access rights to all the
elements in the enclosing class.[35] The following example
demonstrates this: .
//: c08:Sequence.java
// Holds a sequence of Objects.
import com.bruceeckel.simpletest.*;
interface Selector {
boolean end();
Object current();
void next();
}
public class Sequence {
private static Test monitor = new Test();
private Object[] objects;
private int next = 0;
public Sequence(int size) { objects = new Object[size]; }
public void add(Object x) {
if(next < objects.length)
objects[next++] = x;
}
private class SSelector implements Selector {
private int i = 0;
public boolean end() { return i == objects.length; }
public Object current() { return objects[i]; }
public void next() { if(i < objects.length) i++; }
}
public Selector getSelector() { return new SSelector(); }
public static void main(String[] args) {
Sequence sequence = new Sequence(10);
for(int i = 0; i < 10; i++)
sequence.add(Integer.toString(i));
Selector selector = sequence.getSelector();
while(!selector.end()) {
System.out.println(selector.current());
selector.next();
}
monitor.expect(new String[] {
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
});
}
} ///:~
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 theres room left). To fetch each of the objects in a Sequence,
theres an interface called Selector, which allows you to see if youre
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 methodsend( ), current( ),
and next( )refer to objects, which is a reference that isnt
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 it
owned them. This turns out to be very convenient, as you can see in the preceding 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 dont
need a connection between the inner class object and the outer class object, then you can
make the inner class static. This is commonly called a nested class.[36] 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 nested class
means: .
Nested classes are different from ordinary inner classes in another way, as well.
Fields and methods in ordinary inner classes can only be at the outer level of a class, so
ordinary inner classes cannot have static data, static fields, or nested
classes. However, nested classes can have all of these: .
//: c08:Parcel10.java
// Nested classes (static inner classes).
public class Parcel10 {
private static class ParcelContents implements Contents {
private int i = 11;
public int value() { return i; }
}
protected static class ParcelDestination
implements Destination {
private String label;
private ParcelDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
// Nested 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 ParcelDestination(s);
}
public static Contents cont() {
return new ParcelContents();
}
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 nested class
does not have this special this reference, which makes it analogous to a static
method. .
Normally, you cant put any code inside an interface, but a nested class
can be part of an interface. Since the class is static, it
doesnt violate the rules for interfacesthe nested class is only placed inside
the namespace of the interface:
//: c08:IInterface.java
// Nested classes inside interfaces.
public 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 nested class to hold your test
code: .
//: c08:TestBed.java
// Putting test code in a nested class.
public class TestBed {
public TestBed() {}
public 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 dont
need to include it in your shipping product; you can simply delete TestBed$Tester.class
before packaging things up. .
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 dont 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, its 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 nested class (a static inner class), then it doesnt need a reference
to the outer class object. .
[37]It doesnt matter how deeply an inner class may be nestedit
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 theres 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
cant just pass a reference to an enclosing object. In addition, you must use the
syntax .
enclosingClassReference.super();
inside the
constructor. This provides the necessary reference, and the program will then compile. .
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 the entire 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 doesnt really do anything: .
//: c08:BigEgg.java
// An inner class cannot be overriden like a method.
import com.bruceeckel.simpletest.*;
class Egg {
private Yolk y;
protected class Yolk {
public Yolk() { System.out.println("Egg.Yolk()"); }
}
public Egg() {
System.out.println("New Egg()");
y = new Yolk();
}
}
public class BigEgg extends Egg {
private static Test monitor = new Test();
public class Yolk {
public Yolk() { System.out.println("BigEgg.Yolk()"); }
}
public static void main(String[] args) {
new BigEgg();
monitor.expect(new String[] {
"New Egg()",
"Egg.Yolk()"
});
}
} ///:~
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, as you can see from the output. .
This example shows that there isnt 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, its still possible to explicitly inherit from the
inner class: .
//: c08:BigEgg2.java
// Proper inheritance of an inner class.
import com.bruceeckel.simpletest.*;
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 {
private static Test monitor = new Test();
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();
monitor.expect(new String[] {
"Egg2.Yolk()",
"New Egg2()",
"Egg2.Yolk()",
"BigEgg2.Yolk()",
"BigEgg2.Yolk.f()"
});
}
} ///:~
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 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. .
As noted earlier, inner classes can also be created inside code blocks, typically
inside the body of a method. A local inner class cannot have an access specifier because
it isnt part of the outer class, but it does have access to the final variables in
the current code block and all the members of the enclosing class. Heres an example
comparing the creation of a local inner class with an anonymous inner class: .
//: c08:LocalInnerClass.java
// Holds a sequence of Objects.
import com.bruceeckel.simpletest.*;
interface Counter {
int next();
}
public class LocalInnerClass {
private static Test monitor = new Test();
private int count = 0;
Counter getCounter(final String name) {
// A local inner class:
class LocalCounter implements Counter {
public LocalCounter() {
// Local inner class can have a constructor
System.out.println("LocalCounter()");
}
public int next() {
System.out.print(name); // Access local final
return count++;
}
}
return new LocalCounter();
}
// The same thing with an anonymous inner class:
Counter getCounter2(final String name) {
return new Counter() {
// Anonymous inner class cannot have a named
// constructor, only an instance initializer:
{
System.out.println("Counter()");
}
public int next() {
System.out.print(name); // Access local final
return count++;
}
};
}
public static void main(String[] args) {
LocalInnerClass lic = new LocalInnerClass();
Counter
c1 = lic.getCounter("Local inner "),
c2 = lic.getCounter2("Anonymous inner ");
for(int i = 0; i < 5; i++)
System.out.println(c1.next());
for(int i = 0; i < 5; i++)
System.out.println(c2.next());
monitor.expect(new String[] {
"LocalCounter()",
"Counter()",
"Local inner 0",
"Local inner 1",
"Local inner 2",
"Local inner 3",
"Local inner 4",
"Anonymous inner 5",
"Anonymous inner 6",
"Anonymous inner 7",
"Anonymous inner 8",
"Anonymous inner 9"
});
}
} ///:~
Counter returns the next value in a sequence. It is implemented as both a local
class and an anonymous inner class, both of which have the same behaviors and
capabilities. Since the name of the local inner class is not accessible outside the
method, the only justification for using a local inner class instead of an anonymous inner
class is if you need a named constructor and/or overloaded constructor, since an anonymous
inner class can only use instance initialization. .
The only reason to make a local inner class rather than an anonymous inner class is if
you need to make more than one object of that class. .
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 LocalInnerClass.java
include: .
Counter.class LocalInnerClass$2.class LocalInnerClass$1LocalCounter.class LocalInnerClass.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,
its also robust and handles most situations.[38]
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 youve seen a lot of syntax and semantics describing the way inner
classes work, but this doesnt 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 dont I just make the outer class implement that interface?
The answer is If thats all you need, then thats 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
cant always have the convenience of interfacessometimes youre
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 inheritin effectfrom 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 rest 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 in which 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, youll 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,
the approach in the preceding example doesnt 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."
package c08;
class D {}
abstract class E {}
class Z extends D {
E makeE() { return new E() {}; }
}
public class MultiImplementation {
static void takesD(D d) {}
static void takesE(E e) {}
public static void main(String[] args) {
Z z = new Z();
takesD(z);
takesE(z.makeE());
}
} ///:~
If you didnt 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, youd have to
say a Sequence is a Selector, and youd only be able to
have one Selector in existence for a particular Sequence. You can easily
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 doesnt 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 later in the book. If a callback is implemented using a pointer, however, you
must rely on the programmer to behave and not misuse the pointer. As youve 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 solutionmore flexible and
far safer than a pointer. Heres an example:
//: c08:Callbacks.java
// Using inner classes for callbacks
import com.bruceeckel.simpletest.*;
interface Incrementable {
void increment();
}
// Very simple to just implement the interface:
class Callee1 implements Incrementable {
private int i = 0;
public void increment() {
i++;
System.out.println(i);
}
}
class MyIncrement {
void increment() {
System.out.println("Other operation");
}
static void f(MyIncrement mi) { mi.increment(); }
}
// If your class must implement increment() in
// some other way, you must use an inner class:
class Callee2 extends MyIncrement {
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 {
private static Test monitor = new Test();
public static void main(String[] args) {
Callee1 c1 = new Callee1();
Callee2 c2 = new Callee2();
MyIncrement.f(c2);
Caller caller1 = new Caller(c1);
Caller caller2 = new Caller(c2.getCallbackReference());
caller1.go();
caller1.go();
caller2.go();
caller2.go();
monitor.expect(new String[] {
"Other operation",
"1",
"2",
"1",
"2"
});
}
} ///:~
This example also provides a further distinction between implementing an interface in
an outer class versus doing so in an inner class. Callee1 is clearly the simpler
solution in terms of the code. Callee2 inherits from MyIncrement, which
already has a different increment( ) method that does something unrelated to
the one expected by the Incrementable interface. When MyIncrement is
inherited into Callee2, increment( ) cant be overridden for use
by Incrementable, so youre forced to provide a separate implementation using
an inner class. Also note that when you create an inner class, you do not add to or modify
the interface of the outer class. .
Notice that everything except getCallbackReference( ) in Callee2 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 implements Incrementable to provide a hook back
into Callee2but 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 later,
uses the reference to call back into the Callee class. .
The value of the callback is in its flexibility; you can dynamically decide what
methods will be called at run time. The benefit of this will become more evident in
Chapter 14, where callbacks are used everywhere to implement 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
thats designed to solve a particular type of problem. To apply an application
framework, you typically inherit from one or more classes and override some of the
methods. The code that you write in the overridden methods customizes the general solution
provided by that application framework in order to solve your specific problem (this is an
example of the Template Method design
pattern; see Thinking in Patterns (with Java) at www.BruceEckel.com). 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 14, 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 its controlling. That information is supplied during inheritance, when
the template method is implemented. .
First, here is the interface that describes any control event. Its an abstract
class instead of an actual interface because the default behavior is to perform the
control based on time. Thus, some of the implementation is included here: .
//: c08:controller:Event.java
// The common methods for any control event.
package c08.controller;
public abstract class Event {
private long eventTime;
protected final long delayTime;
public Event(long delayTime) {
this.delayTime = delayTime;
start();
}
public void start() { // Allows restarting
eventTime = System.currentTimeMillis() + delayTime;
}
public boolean ready() {
return System.currentTimeMillis() >= eventTime;
}
public abstract void action();
} ///:~
The constructor captures the time (from the time of creation of the object) when you
want the Event to run and then calls start( ), which takes the current
time and adds the delay time to produce the time when the event will occur. Rather than
being included in the constructor, start( ) is a separate method because this
way, it allows you to restart the timer after the event has run out so the Event
object can be reused. For example, if you want a repeating event, you can simply
call start( ) inside your action( ) method. .
ready( ) tells you when its time to run the action( )
method. Of course, ready( ) could be overridden in a derived class to base the
Event on something other than time. .
The following file contains the actual control framework that manages and fires events.
The Event objects are held inside a container object of type ArrayList,
which youll learn more about in Chapter 11. For now, all you need to know is that add( )
will append an Object to the end of the ArrayList, size( )
produces the number of entries in the ArrayList, get( ) will fetch an
element from the ArrayList at a particular index, and remove( ) removes
an element from the ArrayList, given the element number you want to remove. .
//: c08:controller:Controller.java
// With Event, the generic framework for control systems.
package c08.controller;
import java.util.*;
public class Controller {
// An object from java.util to hold Event objects:
private List eventList = new ArrayList();
public void addEvent(Event c) { eventList.add(c); }
public void run() {
while(eventList.size() > 0) {
for(int i = 0; i < eventList.size(); i++) {
Event e = (Event)eventList.get(i);
if(e.ready()) {
System.out.println(e);
e.action();
eventList.remove(i);
}
}
}
}
} ///:~
The run( ) method loops through eventList, hunting for an Event
object thats ready( ) to run. For each one it finds ready( ),
it prints information using the objects toString( ) method, calls
the action( ) method, 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 designhow 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:
Consider a particular implementation of the control framework designed to control
greenhouse functions.[39] 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 in the action( ) implementation. .
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 com.bruceeckel.simpletest.*;
import c08.controller.*;
public class GreenhouseControls extends Controller {
private static Test monitor = new Test();
private boolean light = false;
public class LightOn extends Event {
public LightOn(long delayTime) { super(delayTime); }
public void action() {
// Put hardware control code here to
// physically turn on the light.
light = true;
}
public String toString() { return "Light is on"; }
}
public class LightOff extends Event {
public LightOff(long delayTime) { super(delayTime); }
public void action() {
// Put hardware control code here to
// physically turn off the light.
light = false;
}
public String toString() { return "Light is off"; }
}
private boolean water = false;
public class WaterOn extends Event {
public WaterOn(long delayTime) { super(delayTime); }
public void action() {
// Put hardware control code here.
water = true;
}
public String toString() {
return "Greenhouse water is on";
}
}
public class WaterOff extends Event {
public WaterOff(long delayTime) { super(delayTime); }
public void action() {
// Put hardware control code here.
water = false;
}
public String toString() {
return "Greenhouse water is off";
}
}
private String thermostat = "Day";
public class ThermostatNight extends Event {
public ThermostatNight(long delayTime) {
super(delayTime);
}
public void action() {
// Put hardware control code here.
thermostat = "Night";
}
public String toString() {
return "Thermostat on night setting";
}
}
public class ThermostatDay extends Event {
public ThermostatDay(long delayTime) {
super(delayTime);
}
public void action() {
// Put hardware control code here.
thermostat = "Day";
}
public String toString() {
return "Thermostat on day setting";
}
}
// An example of an action() that inserts a
// new one of itself into the event list:
public class Bell extends Event {
public Bell(long delayTime) { super(delayTime); }
public void action() {
addEvent(new Bell(delayTime));
}
public String toString() { return "Bing!"; }
}
public class Restart extends Event {
private Event[] eventList;
public Restart(long delayTime, Event[] eventList) {
super(delayTime);
this.eventList = eventList;
for(int i = 0; i < eventList.length; i++)
addEvent(eventList[i]);
}
public void action() {
for(int i = 0; i < eventList.length; i++) {
eventList[i].start(); // Rerun each event
addEvent(eventList[i]);
}
start(); // Rerun this Event
addEvent(this);
}
public String toString() {
return "Restarting system";
}
}
public class Terminate extends Event {
public Terminate(long delayTime) { super(delayTime); }
public void action() { System.exit(0); }
public String toString() { return "Terminating"; }
}
} ///:~
Note that light, water, and thermostat 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. .
Most of the Event classes look similar, but Bell and Restart are
special. Bell rings and then 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 and Restart have all the methods of Event and also
appear to have all the methods of the outer class GreenhouseControls. .
Restart is given an array of Event objects that it adds to the
controller. Since Restart( ) is just another Event object, you can also
add a Restart object within Restart.action( ) so that the system
regularly restarts itself. .
The following class configures the system by creating a GreenhouseControls
object and adding various kinds of Event objects. This is an example of the Command
design pattern: .
//: c08:GreenhouseController.java
// Configure and execute the greenhouse system.
// {Args: 5000}
import c08.controller.*;
public class GreenhouseController {
public static void main(String[] args) {
GreenhouseControls gc = new GreenhouseControls();
// Instead of hard-wiring, you could parse
// configuration information from a text file here:
gc.addEvent(gc.new Bell(900));
Event[] eventList = {
gc.new ThermostatNight(0),
gc.new LightOn(200),
gc.new LightOff(400),
gc.new WaterOn(600),
gc.new WaterOff(800),
gc.new ThermostatDay(1400)
};
gc.addEvent(gc.new Restart(2000, eventList));
if(args.length == 1)
gc.addEvent(
gc.new Terminate(Integer.parseInt(args[0])));
gc.run();
}
} ///:~
This class initializes 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 12 asks you to modify this example to do just
that.) If you provide a command-line argument, it uses this to terminate the program after
that many milliseconds (this is used for testing). .
This example should move you toward an appreciation of the value of inner classes,
especially when used within a control framework. However, in Chapter 14 youll 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. .
Interfaces and inner classes are more sophisticated concepts than what youll find
in many OOP languages; for example, theres 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, whereas 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, youll 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, youll eventually
internalize them. .
Solutions to selected exercises can be found in the electronic document The Thinking
in Java Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
[33] This approach was inspired by an
e-mail from Rich Hoffarth. Item 21 in Joshua Blochs Effective Java
(Addison-Wesley, 2001) covers the topic in much more detail.
[34] Thanks to Martin
Danner for asking this question during a seminar.
[35] 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++.
[36] Roughly similar
to nested classes in C++, except that those classes cannot access private members as they
can in Java.
[37] Thanks again to
Martin Danner.
[38] On the other
hand, $ is a meta-character to the Unix shell and so youll 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 werent considering this issue, but
instead thought youd naturally focus on the source-code files.
[39] 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.
|
|
|