Inner classes

Bruce Eckel's Thinking in Java Contents | Prev | Next

//: Parcel1.java
// Creating inner classes
package c07.parcel1;
 
public class Parcel1 {
  class Contents {
    private int i = 11;
    public int value() { return i; }
  }
  class Destination {
    private String label;
    Destination(String whereTo) {
      label = whereTo;
    }
    String readLabel() { return label; }
  }
  // Using inner classes looks just like
  // using any other class, within Parcel1:
  public void ship(String dest) {
    Contents c = new Contents();
    Destination d = new Destination(dest);
  }  
  public static void main(String[] args) {
    Parcel1 p = new Parcel1();
    p.ship("Tanzania");
  }
} ///:~ 

The inner classes, when used inside ship( ), look just like the use of any other classes. Here, the only practical difference is that the names are nested within Parcel1. You’ll see in a while that this isn’t the only difference.

More typically, an outer class will have a method that returns a handle to an inner class, like this:

//: Parcel2.java
// Returning a handle to an inner class
package c07.parcel2;
 
public class Parcel2 {
  class Contents {
    private int i = 11;
    public int value() { return i; }
  }
  class Destination {
    private String label;
    Destination(String whereTo) {
      label = whereTo;
    }
    String readLabel() { return label; }
  }
  public Destination to(String s) {
    return new Destination(s);
  }
  public Contents cont() { 
    return new Contents(); 
  }
  public void ship(String dest) {
    Contents c = cont();
    Destination d = to(dest);
  }  
  public static void main(String[] args) {
    Parcel2 p = new Parcel2();
    p.ship("Tanzania");
    Parcel2 q = new Parcel2();
    // Defining handles to inner classes:
    Parcel2.Contents c = q.cont();
    Parcel2.Destination d = q.to("Borneo");
  }
} ///:~ 

If you want to make an object of the inner class anywhere except from within a non- static method of the outer class, you must specify the type of that object as OuterClassName.InnerClassName, as seen in main( ).

Inner classes and upcasting

//: Parcel3.java
// Returning a handle to an inner class
package c07.parcel3;
 
abstract class Contents {
  abstract public int value();
}
 
interface Destination {
  String readLabel();
}
 
public class Parcel3 {
  private class PContents extends Contents {
    private int i = 11;
    public int value() { return i; }
  }
  protected class PDestination
      implements Destination {
    private String label;
    private PDestination(String whereTo) {
      label = whereTo;
    }
    public String readLabel() { return label; }
  }
  public Destination dest(String s) {
    return new PDestination(s);
  }
  public Contents cont() { 
    return new PContents(); 
  }
}
 
class Test {
  public static void main(String[] args) {
    Parcel3 p = new Parcel3();
    Contents c = p.cont();
    Destination d = p.dest("Tanzania");
    // Illegal -- can't access private class:
    //! Parcel3.PContents c = p.new PContents();
  }
} ///:~ 

Now Contents and Destination represent interfaces available to the client programmer. (The interface, remember, automatically makes all of its members public.) For convenience, these are placed inside a single file, but ordinarily Contents and Destination would each be public in their own files.

In Parcel3, something new has been added: the inner class PContents is private so no one but Parcel3 can access it. PDestination is protected, so no one but Parcel3, classes in the Parcel3 package (since protected also gives package access; that is, protected is also “friendly”), and the inheritors of Parcel3 can access PDestination. This means that the client programmer has restricted knowledge and access to these members. In fact, you can’t even downcast to a private inner class (or a protected inner class unless you’re an inheritor), because you can’t access the name, as you can see in class Test . Thus, the private inner class provides a way for the class designer to completely prevent any type-coding dependencies and to completely hide details about implementation. In addition, extension of an interface is useless from the client programmer’s perspective since the client programmer cannot access any additional methods that aren’t part of the public interface class. This also provides an opportunity for the Java compiler to generate more efficient code.

Normal (non-inner) classes cannot be made private or protected – only public or “friendly.”

Note that Contents doesn’t need to be an abstract class. You could use an ordinary class here as well, but the most typical starting point for such a design is an interface.

Inner classes in methods and scopes

  1. As shown previously, you’re implementing an interface of some kind so that you can create and return a handle.
  2. You’re solving a complicated problem and you want to create a class to aid in your solution, but you don’t want it publicly available.
In the following examples, the previous code will be modified to use:

  1. A class defined within a method
  2. A class defined within a scope inside a method
  3. An anonymous class implementing an interface
  4. An anonymous class extending a class that has a non-default constructor
  5. An anonymous class that performs field initialization
  6. An anonymous class that performs construction using instance initialization (anonymous inner classes cannot have constructors)
//: Destination.java
package c07.innerscopes;
 
interface Destination {
  String readLabel();
} ///:~ 

The point has been made that Contents could be an abstract class, so here it will be in a more natural form, as an interface:

//: Contents.java
package c07.innerscopes;
 
interface Contents {
  int value();
} ///:~ 

Although it’s an ordinary class with an implementation, Wrapping is also being used as a common “interface” to its derived classes:

//: Wrapping.java
package c07.innerscopes;
 
public class Wrapping {
  private int i;
  public Wrapping(int x) { i = x; }
  public int value() { return i; }
} ///:~ 

You’ll notice above that Wrapping has a constructor that requires an argument, to make things a bit more interesting.

The first example shows the creation of an entire class within the scope of a method (instead of the scope of another class):

//: Parcel4.java
// Nesting a class within a method
package c07.innerscopes;
 
public class Parcel4 {
  public Destination dest(String s) {
    class PDestination
        implements Destination {
      private String label;
      private PDestination(String whereTo) {
        label = whereTo;
      }
      public String readLabel() { return label; }
    }
    return new PDestination(s);
  }
  public static void main(String[] args) {
    Parcel4 p = new Parcel4();
    Destination d = p.dest("Tanzania");
  }
} ///:~ 

The class PDestination is part of dest( ) rather than being part of Parcel4. (Also notice that you could use the class identifier PDestination for an inner class inside each class in the same subdirectory without a name clash.) Therefore, PDestination cannot be accessed outside of dest( ). Notice the upcasting that occurs in the return statement – nothing comes out of dest( ) except a handle to the base class Destination. Of course, the fact that the name of the class PDestination is placed inside dest( ) doesn’t mean that PDestination is not a valid object once dest( ) returns.

The next example shows how you can nest an inner class within any arbitrary scope:

//: Parcel5.java
// Nesting a class within a scope
package c07.innerscopes;
 
public class Parcel5 {
  private void internalTracking(boolean b) {
    if(b) {
      class TrackingSlip {
        private String id;
        TrackingSlip(String s) {
          id = s;
        }
        String getSlip() { return id; }
      }
      TrackingSlip ts = new TrackingSlip("slip");
      String s = ts.getSlip();
    }
    // Can't use it here! Out of scope:
    //! TrackingSlip ts = new TrackingSlip("x");
  }
  public void track() { internalTracking(true); }
  public static void main(String[] args) {
    Parcel5 p = new Parcel5();
    p.track();
  }
} ///:~ 

The class TrackingSlip is nested inside the scope of an if statement. This does not mean that the class is conditionally created – it gets compiled along with everything else. However, it’s not available outside the scope in which it is defined. Other than that, it looks just like an ordinary class.

The next example looks a little strange:

//: Parcel6.java
// A method that returns an anonymous inner class
package c07.innerscopes;
 
public class Parcel6 {
  public Contents cont() {
    return new Contents() {
      private int i = 11;
      public int value() { return i; }
    }; // Semicolon required in this case
  }
  public static void main(String[] args) {
    Parcel6 p = new Parcel6();
    Contents c = p.cont();
  }
} ///:~ 

The cont( ) method combines the creation of the return value with the definition of the class that represents that return value! In addition, the class is anonymous – it has no name. To make matters a bit worse, it looks like you’re starting out to create a Contents object:

return new Contents()

but then, before you get to the semicolon, you say, “But wait, I think I’ll slip in a class definition”:

return new Contents() {
  private int i = 11;
  public int value() { return i; }
};

What this strange syntax means is “create an object of an anonymous class that’s inherited from Contents.” The handle returned by the new expression is automatically upcast to a Contents handle. The anonymous inner class syntax is a shorthand for:

class MyContents extends Contents {
  private int i = 11;
  public int value() { return i; }
}
return new MyContents();

In the anonymous inner class, Contents is created using a default constructor. The following code shows what to do if your base class needs a constructor with an argument:

//: Parcel7.java
// An anonymous inner class that calls the 
// base-class constructor
package c07.innerscopes;
 
public class Parcel7 {
  public Wrapping wrap(int x) {
    // Base constructor call:
    return new Wrapping(x) { 
      public int value() {
        return super.value() * 47;
      }
    }; // Semicolon required
  }
  public static void main(String[] args) {
    Parcel7 p = new Parcel7();
    Wrapping w = p.wrap(10);
  }
} ///:~ 

That is, you simply pass the appropriate argument to the base-class constructor, seen here as the x passed in new Wrapping(x) . An anonymous class cannot have a constructor where you would normally call super( ).

In both of the previous examples, the semicolon doesn’t mark the end of the class body (as it does in C++). Instead, it marks the end of the expression that happens to contain the anonymous class. Thus, it’s identical to the use of the semicolon everywhere else.

//: Parcel8.java
// An anonymous inner class that performs 
// initialization. A briefer version
// of Parcel5.java.
package c07.innerscopes;
 
public class Parcel8 {
  // Argument must be final to use inside 
  // anonymous inner class:
  public Destination dest(final String dest) {
    return new Destination() {
      private String label = dest;
      public String readLabel() { return label; }
    };
  }
  public static void main(String[] args) {
    Parcel8 p = new Parcel8();
    Destination d = p.dest("Tanzania");
  }
} ///:~ 

If you’re defining an anonymous inner class and want to use an object that’s defined outside the anonymous inner class, the compiler requires that the outside object be final. This is why the argument to dest( ) is final. If you forget, you’ll get a compile-time error message.

As long as you’re simply assigning a field, the above approach is fine. But what if you need to perform some constructor-like activity? With Java 1.1 instance initialization , you can, in effect, create a constructor for an anonymous inner class:

//: Parcel9.java
// Using "instance initialization" to perform 
// construction on an anonymous inner class
package c07.innerscopes;
 
public class Parcel9 {
  public Destination 
  dest(final String dest, final float price) {
    return new Destination() {
      private int cost;
      // Instance initialization for each object:
      {
        cost = Math.round(price);
        if(cost > 100)
          System.out.println("Over budget!");
      }
      private String label = dest;
      public String readLabel() { return label; }
    };
  }
  public static void main(String[] args) {
    Parcel9 p = new Parcel9();
    Destination d = p.dest("Tanzania", 101.395F);
  }
} ///:~ 

Inside the instance initializer you can see code that couldn’t be executed as part of a field initializer (that is, the if statement). So in effect, an instance initializer is the constructor for an anonymous inner class. Of course, it’s limited; you can’t overload instance initializers so you can have only one of these constructors.

The link to the outer class

//: Sequence.java
// Holds a sequence of Objects
 
interface Selector {
  boolean end();
  Object current();
  void next();
}
 
public class Sequence {
  private Object[] o;
  private int next = 0;
  public Sequence(int size) {
    o = new Object[size];
  }
  public void add(Object x) {
    if(next < o.length) {
      o[next] = x;
      next++;
    }
  }
  private class SSelector implements Selector {
    int i = 0;
    public boolean end() {
      return i == o.length;
    }
    public Object current() {
      return o[i];
    }
    public void next() {
      if(i < o.length) i++;
    }
  }
  public Selector getSelector() {
    return new SSelector();
  }
  public static void main(String[] args) {
    Sequence s = new Sequence(10);
    for(int i = 0; i < 10; i++)
      s.add(Integer.toString(i));
    Selector sl = s.getSelector();    
    while(!sl.end()) {
      System.out.println((String)sl.current());
      sl.next();
    }
  }
} ///:~ 

The Sequence is simply a fixed-sized array of Object with a class wrapped around it. You call add( ) to add a new Object to the end of the sequence (if there’s room left). To fetch each of the objects in a Sequence, there’s an interface called Selector, which allows you to see if you’re at the end( ), to look at the current( ) Object, and to move to the next( ) Object in the Sequence. Because Selector is an interface, many other classes can implement the interface in their own ways, and many methods can take the interface as an argument, in order to create generic code.

Here, the SSelector is a private class that provides Selector functionality. In main( ), you can see the creation of a Sequence, followed by the addition of a number of String objects. Then, a Selector is produced with a call to getSelector( ) and this is used to move through the Sequence and select each item.

At first, the creation of SSelector looks like just another inner class. But examine it closely. Note that each of the methods end( ), current( ), and next( ) refer to o, which is a handle that isn’t part of SSelector, but is instead a private field in the enclosing class. However, the inner class can access methods and fields from the enclosing class as if they owned them. This turns out to be very convenient, as you can see in the above example.

static inner classes

  1. You don’t need an outer-class object in order to create an object of a static inner class.
  2. You can’t access an outer-class object from an object of a static inner class.
There are some restrictions: static members can be at only the outer level of a class, so inner classes cannot have static data or static inner classes.

//: Parcel10.java
// Static inner classes
package c07.parcel10;
 
abstract class Contents {
  abstract public int value();
}
 
interface Destination {
  String readLabel();
}
 
public class Parcel10 {
  private static class PContents 
  extends Contents {
    private int i = 11;
    public int value() { return i; }
  }
  protected static class PDestination
      implements Destination {
    private String label;
    private PDestination(String whereTo) {
      label = whereTo;
    }
    public String readLabel() { return label; }
  }
  public static Destination dest(String s) {
    return new PDestination(s);
  }
  public static Contents cont() {
    return new PContents();
  }
  public static void main(String[] args) {
    Contents c = cont();
    Destination d = dest("Tanzania");
  }
} ///:~ 

In main( ), no object of Parcel10 is necessary; instead you use the normal syntax for selecting a static member to call the methods that return handles to Contents and Destination.

Normally you can't put any code inside an interface, but a static inner class can be part of an interface. Since the class is static it doesn't violate the rules for interfaces – the static inner class is only placed inside the namespace of the interface:

//: IInterface.java
// Static inner classes inside interfaces
 
class IInterface {
  static class Inner {
    int i, j, k;
    public Inner() {}
    void f() {}
  }
} ///:~ 

//: TestBed.java
// Putting test code in a static inner class
 
class TestBed {
  TestBed() {}
  void f() { System.out.println("f()"); }
  public static class Tester {
    public static void main(String[] args) {
      TestBed t = new TestBed();
      t.f();
    }
  }
} ///:~ 

This generates a separate class called TestBed$Tester (to run the program you say java TestBed$Tester ). You can use this class for testing, but you don't need to include it in your shipping product.

Referring to the outer class object

If you need to produce the handle to the outer class object, you name the outer class followed by a dot and this. For example, in the class Sequence.SSelector, any of its methods can produce the stored handle to the outer class Sequence by saying Sequence.this. The resulting handle is automatically the correct type. (This is known and checked at compile time, so there is no run-time overhead.)

Sometimes you want to tell some other object to create an object of one of its inner classes. To do this you must provide a handle to the other outer class object in the new expression, like this:

//: Parcel11.java
// Creating inner classes
package c07.parcel11;
 
public class Parcel11 {
  class Contents {
    private int i = 11;
    public int value() { return i; }
  }
  class Destination {
    private String label;
    Destination(String whereTo) {
      label = whereTo;
    }
    String readLabel() { return label; }
  }
  public static void main(String[] args) {
    Parcel11 p = new Parcel11();
    // Must use instance of outer class
    // to create an instances of the inner class:
    Parcel11.Contents c = p.new Contents();
    Parcel11.Destination d =
      p.new Destination("Tanzania");
  }
} ///:~ 

To create an object of the inner class directly, you don’t follow the same form and refer to the outer class name Parcel11 as you might expect, but instead you must use an object of the outer class to make an object of the inner class:

Parcel11.Contents c = p.new Contents();

Thus, it’s not possible to create an object of the inner class unless you already have an object of the outer class. This is because the object of the inner class is quietly connected to the object of the outer class that it was made from. However, if you make a static inner class, then it doesn’t need a handle to the outer class object.

Inheriting from inner classes

//: InheritInner.java
// Inheriting an inner class
 
class WithInner {
  class Inner {}
}
 
public class InheritInner 
    extends WithInner.Inner {
  //! InheritInner() {} // Won't compile
  InheritInner(WithInner wi) {
    wi.super();
  }
  public static void main(String[] args) {
    WithInner wi = new WithInner();
    InheritInner ii = new InheritInner(wi);
  }
} ///:~ 

You can see that InheritInner is extending only the inner class, not the outer one. But when it comes time to create a constructor, the default one is no good and you can’t just pass a handle to an enclosing object. In addition, you must use the syntax

enclosingClassHandle.super();

Can inner classes be overridden?

//: BigEgg.java
// An inner class cannot be overriden 
// like a method
 
class Egg {
  protected class Yolk {
    public Yolk() {
      System.out.println("Egg.Yolk()");
    }
  }
  private Yolk y;
  public Egg() {
    System.out.println("New Egg()");
    y = new Yolk();
  }
}
 
public class BigEgg extends Egg {
  public class Yolk {
    public Yolk() {
      System.out.println("BigEgg.Yolk()");
    }
  }
  public static void main(String[] args) {
    new BigEgg();
  }
} ///:~ 

The default constructor is synthesized automatically by the compiler, and this calls the base-class default constructor. You might think that since a BigEgg is being created, the “overridden” version of Yolk would be used, but this is not the case. The output is:

New Egg()
Egg.Yolk()

This example simply shows that there isn’t any extra inner class magic going on when you inherit from the outer class. However, it’s still possible to explicitly inherit from the inner class:

//: BigEgg2.java
// Proper inheritance of an inner class
 
class Egg2 {
  protected class Yolk {
    public Yolk() {
      System.out.println("Egg2.Yolk()");
    }
    public void f() {
      System.out.println("Egg2.Yolk.f()");
    }
  }
  private Yolk y = new Yolk();
  public Egg2() {
    System.out.println("New Egg2()");
  }
  public void insertYolk(Yolk yy) { y = yy; }
  public void g() { y.f(); }
}
 
public class BigEgg2 extends Egg2 {
  public class Yolk extends Egg2.Yolk {
    public Yolk() {
      System.out.println("BigEgg2.Yolk()");
    }
    public void f() {
      System.out.println("BigEgg2.Yolk.f()");
    }
  }
  public BigEgg2() { insertYolk(new Yolk()); }
  public static void main(String[] args) {
    Egg2 e2 = new BigEgg2();
    e2.g();
  }
} ///:~ 

Now BiggEgg2.Yolk explicitly extends Egg2.Yolk and overrides its methods. The method insertYolk( ) allows BigEgg2 to upcast one of its own Yolk objects into the y handle in Egg2, so when g( ) calls y.f( ) the overridden version of f( ) is used. The output is:

Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk.f()

The second call to Egg2.Yolk( ) is the base-class constructor call of the BigEgg2.Yolk constructor. You can see that the overridden version of f( ) is used when g( ) is called.

Inner class identifiers

InheritInner.class
WithInner$Inner.class
WithInner.class

If inner classes are anonymous, the compiler simply starts generating numbers as inner class identifiers. If inner classes are nested within inner classes, their names are simply appended after a ‘ $’ and the outer class identifier(s).

Although this scheme of generating internal names is simple and straightforward, it’s also robust and handles most situations. [30] Since it is the standard naming scheme for Java, the generated files are automatically platform-independent. (Note that the Java compiler is changing your inner classes in all sorts of other ways in order to make them work.)

Why inner classes: control frameworks

An application framework is a class or a set of classes that’s designed to solve a particular type of problem. To apply an application framework, you inherit from one or more classes and override some of the methods. The code you write in the overridden methods customizes the general solution provided by that application framework to solve your specific problem. The control framework is a particular type of application framework dominated by the need to respond to events; a system that primarily responds to events is called an event-driven system. One of the most important problems in application programming is the graphical user interface (GUI), which is almost entirely event-driven. As you will see in Chapter 13, the Java 1.1 AWT is a control framework that elegantly solves the GUI problem using inner classes.

//: Event.java
// The common methods for any control event
package c07.controller;
 
abstract public class Event {
  private long evtTime;
  public Event(long eventTime) {
    evtTime = eventTime;
  }
  public boolean ready() {
    return System.currentTimeMillis() >= evtTime;
  }
  abstract public void action();
  abstract public String description();
} ///:~ 

The constructor simply captures the time when you want the Event to run, while ready( ) tells you when it’s time to run it. Of course, ready( ) could be overridden in a derived class to base the Event on something other than time.

action( ) is the method that’s called when the Event is ready( ), and description( ) gives textual information about the Event.

The next file contains the actual control framework that manages and fires events. The first class is really just a “helper” class whose job is to hold Event objects. You could replace it with any appropriate collection, and in Chapter 8 you’ll discover other collections that will do the trick without requiring you to write this extra code:

//: Controller.java
// Along with Event, the generic
// framework for all control systems:
package c07.controller;
 
// This is just a way to hold Event objects.
class EventSet {
  private Event[] events = new Event[100];
  private int index = 0;
  private int next = 0;
  public void add(Event e) {
    if(index >= events.length)
      return; // (In real life, throw exception)
    events[index++] = e;
  }
  public Event getNext() {
    boolean looped = false;
    int start = next;
    do {
      next = (next + 1) % events.length;
      // See if it has looped to the beginning:
      if(start == next) looped = true;
      // If it loops past start, the list 
      // is empty:
      if((next == (start + 1) % events.length)
         && looped)
        return null;
    } while(events[next] == null);
    return events[next];
  }
  public void removeCurrent() {
    events[next] = null;
  }
}
 
public class Controller {
  private EventSet es = new EventSet();
  public void addEvent(Event c) { es.add(c); }
  public void run() {
    Event e;
    while((e = es.getNext()) != null) {
      if(e.ready()) {
        e.action();
        System.out.println(e.description());
        es.removeCurrent();
      }
    }
  }
} ///:~ 

EventSet arbitrarily holds 100 Events. (If a “real” collection from Chapter 8 is used here you don’t need to worry about its maximum size, since it will resize itself). The index is used to keep track of the next available space, and next is used when you’re looking for the next Event in the list, to see whether you’ve looped around. This is important during a call to getNext( ), because Event objects are removed from the list (using removeCurrent( )) once they’re run, so getNext( ) will encounter holes in the list as it moves through it.

Note that removeCurrent( ) doesn’t just set some flag indicating that the object is no longer in use. Instead, it sets the handle to null. This is important because if the garbage collector sees a handle that’s still in use then it can’t clean up the object. If you think your handles might hang around (as they would here), then it’s a good idea to set them to null to give the garbage collector permission to clean them up.

  1. To express the entire implementation of a control-framework application in a single class, thereby encapsulating everything that’s unique about that implementation. Inner classes are used to express the many different kinds of action( ) necessary to solve the problem. In addition, the following example uses private inner classes so the implementation is completely hidden and can be changed with impunity.
  2. Inner classes keep this implementation from becoming awkward, since you’re able to easily access any of the members in the outer class. Without this ability the code might become unpleasant enough that you’d end up seeking an alternative.
Consider a particular implementation of the control framework designed to control greenhouse functions. [31] Each action is entirely different: turning lights, water, and thermostats on and off, ringing bells, and restarting the system. But the control framework is designed to easily isolate this different code. For each type of action you inherit a new Event inner class, and write the control code inside of action( ).

As is typical with an application framework, the class GreenhouseControls is inherited from Controller:

//: GreenhouseControls.java
// This produces a specific application of the
// control system, all in a single class. Inner
// classes allow you to encapsulate different
// functionality for each type of event.
package c07.controller;
 
public class GreenhouseControls 
    extends Controller {
  private boolean light = false;
  private boolean water = false;
  private String thermostat = "Day";
  private class LightOn extends Event {
    public LightOn(long eventTime) {
      super(eventTime);
    }
    public void action() {
      // Put hardware control code here to 
      // physically turn on the light.
      light = true;
    }
    public String description() {
      return "Light is on";
    }
  }
  private class LightOff extends Event {
    public LightOff(long eventTime) {
      super(eventTime);
    }
    public void action() {
      // Put hardware control code here to 
      // physically turn off the light.
      light = false;
    }
    public String description() {
      return "Light is off";
    }
  }
  private class WaterOn extends Event {
    public WaterOn(long eventTime) {
      super(eventTime);
    }
    public void action() {
      // Put hardware control code here
      water = true;
    }
    public String description() {
      return "Greenhouse water is on";
    }
  }
  private class WaterOff extends Event {
    public WaterOff(long eventTime) {
      super(eventTime);
    }
    public void action() {
      // Put hardware control code here
      water = false;
    }
    public String description() {
      return "Greenhouse water is off";
    }
  }
  private class ThermostatNight extends Event {
    public ThermostatNight(long eventTime) {
      super(eventTime);
    }
    public void action() {
      // Put hardware control code here
      thermostat = "Night";
    }
    public String description() {
      return "Thermostat on night setting";
    }
  }
  private class ThermostatDay extends Event {
    public ThermostatDay(long eventTime) {
      super(eventTime);
    }
    public void action() {
      // Put hardware control code here
      thermostat = "Day";
    }
    public String description() {
      return "Thermostat on day setting";
    }
  }
  // An example of an action() that inserts a 
  // new one of itself into the event list:
  private int rings;
  private class Bell extends Event {
    public Bell(long eventTime) {
      super(eventTime);
    }
    public void action() {
      // Ring bell every 2 seconds, rings times:
      System.out.println("Bing!");
      if(--rings > 0)
        addEvent(new Bell(
          System.currentTimeMillis() + 2000));
    }
    public String description() {
      return "Ring bell";
    }
  }
  private class Restart extends Event {
    public Restart(long eventTime) {
      super(eventTime);
    }
    public void action() {
      long tm = System.currentTimeMillis();
      // Instead of hard-wiring, you could parse
      // configuration information from a text
      // file here:
      rings = 5;
      addEvent(new ThermostatNight(tm));
      addEvent(new LightOn(tm + 1000));
      addEvent(new LightOff(tm + 2000));
      addEvent(new WaterOn(tm + 3000));
      addEvent(new WaterOff(tm + 8000));
      addEvent(new Bell(tm + 9000));
      addEvent(new ThermostatDay(tm + 10000));
      // Can even add a Restart object!
      addEvent(new Restart(tm + 20000));
    }
    public String description() {
      return "Restarting system";
    }
  }
  public static void main(String[] args) {
    GreenhouseControls gc = 
      new GreenhouseControls();
    long tm = System.currentTimeMillis();
    gc.addEvent(gc.new Restart(tm));
    gc.run();
  } 
} ///:~ 

Note that light, water, thermostat, and rings all belong to the outer class GreenhouseControls, and yet the inner classes have no problem accessing those fields. Also, most of the action( ) methods also involve some sort of hardware control, which would most likely involve calls to non-Java code.

Most of the Event classes look similar, but Bell and Restart are special. Bell rings, and if it hasn’t yet rung enough times it adds a new Bell object to the event list, so it will ring again later. Notice how inner classes almost look like multiple inheritance: Bell has all the methods of Event and it also appears to have all the methods of the outer class GreenhouseControls.

Restart is responsible for initializing the system, so it adds all the appropriate events. Of course, a more flexible way to accomplish this is to avoid hard-coding the events and instead read them from a file. (An exercise in Chapter 10 asks you to modify this example to do just that.) Since Restart( ) is just another Event object, you can also add a Restart object within Restart.action( ) so that the system regularly restarts itself. And all you need to do in main( ) is create a GreenhouseControls object and add a Restart object to get it going.

This example should move you a long way toward appreciating the value of inner classes, especially when used within a control framework. However, in the latter half of Chapter 13 you’ll see how elegantly inner classes are used to describe the actions of a graphical user interface. By the time you finish that section you should be fully convinced.


[29] This is very different from the design of nested classes in C++, which is simply a name-hiding mechanism. There is no link to an enclosing object and no implied permissions in C++.

[30] On the other hand, ‘$’ is a meta-character to the Unix shell and so you’ll sometimes have trouble when listing the .class files. This is a bit strange coming from Sun, a Unix-based company. My guess is that they weren’t considering this issue, but instead thought you’d naturally focus on the source-code files.

[31] For some reason this has always been a pleasing problem for me to solve; it came from C++ Inside & Out , but Java allows a much more elegant solution.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: August 20, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT When you look at natural user interfaces as a developer, it isn't just fun and games. There are some very serious, real-world usage models of how things can help make the world a better place – things like Intel® RealSense™ technology. Check out this upcoming eSeminar and join the panel of experts, both from inside and outside of Intel, as they discuss how natural user interfaces will likely be getting adopted in a wide variety …

  • "Security" is the number one issue holding business leaders back from the cloud. But does the reality match the perception? Keeping data close to home, on premises, makes business and IT leaders feel inherently more secure. But the truth is, cloud solutions can offer companies real, tangible security advantages. Before you assume that on-site is the only way to keep data safe, it's worth taking a comprehensive approach to evaluating risks. Doing so can lead to big benefits.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds