Java in a Nutshell

Previous Chapter 5
Inner Classes and Other New Language Features
Next
 

5.4 Local Classes

A local class is a class declared locally within a block of Java code. Typically, a local class is defined within a method, but local classes can also be defined within static initializers and instance initializers of a class. (Instance initializers are a new feature of Java 1.1 that we'll see later in this chapter.)

Because Java code can only appear within class definitions, all local classes are nested within a containing class. For this reason, local classes share many of the features of member classes. It is usually more appropriate, however, to think of them as an entirely separate kind of inner class. A local class has approximately the same relationship to a member class as a local variable has to an instance variable of a class. Local classes have the following interesting features:

The first defining characteristic of a local class is obviously that it is local. Like a local variable, the name of a local class is only valid within the block of code in which it was defined (and within any blocks nested with that block, of course). In many cases, this is not a problem. If a helper class is used only within a single method of a containing class, there is no reason that that helper cannot be coded as a local class rather than a member class. Example 5.5 shows how we can modify the enumerate() method of the LinkedList class we saw in previous examples so that it defines the Enumerator class as a local class, rather than as a member class. By doing this, we move the definition of the helper class even closer to the location where it is used and hopefully improve the clarity of the code even further.

Example 5.5: Defining and Using a Local Class

import java.util.*;
public class LinkedList 
{
  // Our nested top-level interface.  Body omitted here...
  public interface Linkable { ... } 
  // The head of the list
  /* private */ Linkable head;  
  // Method bodies omitted here.
  public void addToHead(Linkable node) { ... }
  public Linkable removeHead() { ...  }
  // This method creates and returns an Enumeration object for this 
  // LinkedList.
  public Enumeration enumerate() 
  {
    // Here's the definition of Enumerator as a local class.
    class Enumerator implements Enumeration {
      Linkable current;
      public Enumerator() { this.current = LinkedList.this.head; }
      public boolean hasMoreElements() {  return (current != null); }
      public Object nextElement() {
        if (current == null) throw new NoSuchElementException("LinkedList");
        Object value = current;
        current = current.getNext();
        return value;
      }
    }
    // Create and return an instance of the Enumerator class defined here.
    return new Enumerator();
  }
}

How Local Classes Work

A local class is able to refer to fields and methods in its containing class for exactly the same reason that a member class can--it is passed a hidden reference to the containing class in its constructor, and it saves that reference away in a private field added by the compiler. Also like member classes, local classes can use private fields and methods of their containing class, because the compiler inserts any required accessor methods. [9]

[9] As previously noted, bugs in the compiler prevent this from working correctly in the current versions of the JDK.

What makes local classes different from member classes is that they have the ability to refer to local variables from the scope that defines them. The crucial restriction on this ability, however, is that local classes can only reference local variables and parameters that are declared final. The reason for this is apparent from the implementation. A local class can use local variables because the compiler automatically gives the class a private instance field to hold a copy of each local variable the class refers to. The compiler also adds hidden arguments to each of the local class constructors to initialize these automatically created private fields to the appropriate values. So, in fact, a local class does not actually access local variables, but merely its own private copies of them. The only way this can work correctly is if the local variables are declared final, so that they are guaranteed not to change. With this guarantee, the local class can be assured that its internal copies of the variables are "in sync" with the real local variables.

New Syntax for Local Classes

In Java 1.0, only fields, methods, and classes may be declared final. The addition of local classes to Java 1.1 has required a liberalization in the use of the final modifier. It can now be applied to local variables, arguments to methods, and even to the exception parameter of a catch statement (Local classes can refer to catch parameters, just as they can refer to method parameters, as long as they are in scope and are declared final.) The meaning of the final modifier remains the same in these new uses: once the local variable or argument has been assigned a value, that value may not be changed.

Instances of local classes, like instances of member classes, have an enclosing instance that is implicitly passed to all constructors of the local class. Local classes can use the same new this syntax that member classes do to explicitly refer to members of enclosing classes. Local classes cannot use the new new and super syntax used by member classes, however.

Restrictions on Local Classes

Like member classes, and for the same reasons, local classes cannot contain fields, methods, or classes that are declared static. static members must be declared at the top level. Since nested interfaces are implicitly static, local classes may not contain nested interface definitions.

Another restriction on local classes is that they cannot be declared public, protected, private, or static. These modifiers are all used for members of classes, and are not allowed with local variable declarations; for the same reason they are not allowed with local class declarations.

And finally, a local class, like a member class, cannot have the same name as any of its enclosing classes.

Typical Uses of Local Classes

One common application of local classes is to implement "event listeners" for use with the new event model implemented by AWT and JavaBeans in Java 1.1. An event listener is a class that implements a specific "listener" interface. This listener is registered with an AWT component, such as a Button, or with some other "event source." When the Button (or other source) is clicked (or activated in some way), it responds to this event by invoking a method of the event listener. Since Java does not support method pointers, implementing a pre-defined interface is Java's way of defining a "callback" that is notified when some event occurs. The classes used to implement these interfaces are often called adapter classes. Working with adapter classes can become quite cumbersome when they all must be defined as top-level classes. But the introduction of local classes makes them much easier to use. Example 5.6 shows a local class used as an adapter class for handling GUI events. [10] This example also shows how a local class can make use of a method parameter that is declared final.

[10] As we'll see in the next section, this adapter class could be written more succinctly as an anonymous class.

Example 5.6: Using a Local Class as an Adapter

import java.awt.*;
import java.awt.event.*;
// This class implements the functionality of some application.
public class Application {
  // These are some constants used as the command argument to doCommand().
  static final int save = 1;
  static final int load = 2;
  static final int quit = 3;
  // This method dispatches all the commands to the application.  
  // Body omitted.
  void doCommand(int command) { }
  // Other methods of the application omitted...
}
// This class defines a GUI for the application.
class GUI extends Frame {
  Application app;  // holds a reference to the Application instance
  // Constructor and other methods omitted here...
  
  // This is a convenience method used to create menu items with
  // a specified label, keyboard shortcut, and command to be executed.
  // We declare the "command" argument final so the local
  // ActionListener class can refer to it.
  MenuItem createMenuItem(String label, char shortcut, final int command) 
  {
    // First we create a MenuItem object.
    MenuItem item = new MenuItem(label, new MenuShortcut(shortcut));
    // Then we define a local class to serve as our ActionListener.
    class MenuItemListener implements ActionListener {
      // Note that this method uses the app field of the enclosing class
      // and the (final) command argument from its containing scope.
      public void actionPerformed(ActionEvent e) {
        app.doCommand(command);
      }
    }
    // Next, we create an instance of our local class that will be
    // the particular action listener for this MenuItem.
    ActionListener listener = new MenuItemListener();
    // Then we register the ActionListener for our new MenuItem.
    item.addActionListener(listener);
    // And return the item, ready to be inserted into a menu.
    return item;
  }
}

createMenuItem() is the method of interest in Example 5.6. It creates a MenuItem object with the specified label and keyboard shortcut, and then uses a local class to create an ActionListener object for the menu item. The ActionListener is responsible for translating the selection of the MenuItem into an invocation of the application's doCommand() method. Note that the command method parameter is declared final so it can be used by the local class. Also note that the local class uses the app field of the class that contains it. Because this is an instance variable instead of a local variable, it does not need to be declared final.

A local class can use fields defined within the local class itself or inherited by the local class, final local variables and final parameters in the scope of the local class definition, and fields defined by or inherited by the containing class. Example 5.7 is a program that demonstrates these various fields and variables that are accessible to a local class. If you can make sense of the code, you have a good understanding of local classes.

Example 5.7: Fields and Variables Accessible to a Local Class

class A { protected char a = 'a'; }
class B { protected char b = 'b'; }
public class C extends A
{
  private char c = 'c';         // Private fields visible to local class.
  public static char d = 'd';
  public void createLocalObject(final char e)
  {
    final char f = 'f';
    int i = 0;                  // i not final; not usable by local class.
    class Local extends B
    {
      char g = 'g';
      public void printVars()
      {
        // All of these fields and variables are accessible to this class.
        System.out.println(g);  // (this.g) g is a field of this class.
        System.out.println(f);  // f is a final local variable.
        System.out.println(e);  // e is a final local argument.
        System.out.println(d);  // (C.this.d) d -- field of containing class.
        System.out.println(c);  // (C.this.c) c -- field of containing class.
        System.out.println(b);  // b is inherited by this class.
        System.out.println(a);  // a is inherited by the containing class.
      }
    }
    Local l = this.new Local(); // Create an instance of the local class
    l.printVars();              // and call its printVars() method.
  }
  
  public static void main(String[] args)
  {
    // Create an instance of the containing class, and invoke the
    // method that defines and creates the local class.
    C c = new C();
    c.createLocalObject('e');   // pass a value for final parameter e.
  }
}


Previous Home Next
Member Classes Book Index Anonymous Classes

Java in a Nutshell Java Language Reference Java AWT Java Fundamental Classes Exploring Java