Classes within classes

Learn the basics of nested top-level classes and inner classes

While teaching Java, I often find that students try to declare methods within other methods. However, unlike the Pascal language -- which allows procedures (roughly equivalent to methods) to nest inside other procedures -- Java does not allow methods to nest inside other methods. As a result, the Java compiler generates an error when it encounters the following code fragment, which nests method innerMethod() inside method outerMethod():

void outerMethod ()
{
   void innerMethod ()
   {
   }
}

In contrast to method nesting, Java, beginning with Java Language Specification 1.1, supports class nesting; the Java compiler allows one class to appear inside another class. The following code fragment demonstrates the nesting of class innerClass inside class outerClass:

class outerClass
{
   class innerClass
   {
   }
}

Why does Java support class nesting, and what kinds of nested classes does Java support? This Java 101 installment answers these questions. Once you finish reading this article, you will have gained a good working knowledge of class nesting and can use that knowledge to write powerful Java programs. To begin, let's find out why Java supports class nesting.

Note
With the release of JDK 1.1 (which includes Java 1.1), Sun released the Inner Classes Specification document. That document thoroughly covers nested top-level classes and inner classes. I strongly encourage you to review that document once you finish reading this article.

Why does Java support class nesting?

Java doesn't have to support class nesting. In fact, if you study the Inner Classes Specification document, you will discover workarounds to class nesting. However, there are at least two benefits to Java's support for class nesting:

  • An improvement in source code clarity
  • A reduction in name conflicts

Source code clarity improves with class nesting because you declare a class closer to those objects it must manipulate and allow that class's methods to directly access object fields and call object methods -- even private fields and methods -- of enclosing classes. To understand that benefit, consider a scenario in which a program must iterate over an array of Job objects that exist within an Employee object:

Listing 1. JobIterator1.java

// JobIterator1.java
class Job
{
   private String jobTitle;
   Job (String jobTitle)
   {
      this.jobTitle = jobTitle;
   }
   public String toString ()
   {
      return jobTitle;
   }
}
class Employee
{
   private String name;
   private Job [] jobs;
   private int jobIndex = 0;
   Employee (String name, Job [] jobs)
   {
      this.name = name;
      this.jobs = jobs;
   }
   String getName ()
   {
      return name;
   }
   boolean hasMoreJobs ()
   {
      return jobIndex < jobs.length;
   }
   Job nextJob ()
   {
      return !hasMoreJobs () ? null : jobs [jobIndex++];
   }
}
class JobIterator1
{
   public static void main (String [] args)
   {
      Job [] jobs = { new Job ("Janitor"), new Job ("Delivery Person") };
      Employee e = new Employee ("John Doe", jobs);
      System.out.println (e.getName () + " works the following jobs:\n");
      while (e.hasMoreJobs ())
         System.out.println (e.nextJob ());
   }
}

When run, JobIterator1 produces the following output:

John Doe works the following jobs:
Janitor
Delivery Person

JobIterator1 consists of classes Job, Employee, and JobIterator1. Job encapsulates a job title, and Employee encapsulates an employee name and an array of those jobs the employee performs. JobIterator1 contains a main() method that creates Job and Employee objects, and prints the employee's name and jobs.

A close look at Employee reveals methods hasMoreJobs() and nextJob(). Together, those methods make up an iterator. When an Employee object initializes, an internal index in the private jobs array sets to zero. The hasMoreJobs() method returns a Boolean true value if that index's value is less than the jobs array's length. nextJob() uses that index's value to return a Job object from the array at that index -- and increments that index's value so the next call to nextJob() returns a reference to the next Job object.

JobIterator1 does feature problems. First, you can't restart an iteration after it completes. However, you could easily solve that problem by adding some sort of reset() method to Employee that assigns 0 to jobIndex. A second, more serious problem is the inability to create multiple iterators for a single Employee object. That problem arises from hasMoreJobs() and nextJob() being hardcoded within Employee. To overcome both problems, developers typically declare an iterator class whose objects iterate over the jobs array. Once an iterator finishes, a program can start a new iteration by creating a new iterator object. Also, by creating multiple iterator objects, a program can perform multiple iterations over the same Employee object's jobs array. Listing 2 demonstrates the use of an iterator class named JobIterator:

Listing 2. JobIterator2.java

// JobIterator2.java
class Job
{
   private String jobTitle;
   Job (String jobTitle)
   {
      this.jobTitle = jobTitle;
   }
   public String toString ()
   {
      return jobTitle;
   }
}
class Employee
{
   private String name;
   private Job [] jobs;
   Employee (String name, Job [] jobs)
   {
      this.name = name;
      this.jobs = jobs;
   }
   String getName ()
   {
      return name;
   }
   JobIterator getJobIterator ()
   {
      return new JobIterator (jobs);
   }
}
class JobIterator
{
   private Job [] jobs;
   private int jobIndex = 0;
   JobIterator (Job [] jobs)
   {
      this.jobs = jobs;
   }
   boolean hasMoreJobs ()
   {
      return jobIndex < jobs.length;
   }
   Job nextJob ()
   {
      return !hasMoreJobs () ? null : jobs [jobIndex++];
   }
}
class JobIterator2
{
   public static void main (String [] args)
   {
      Job [] jobs = { new Job ("Janitor"), new Job ("Delivery Person") };
      Employee e = new Employee ("John Doe", jobs);
      System.out.println (e.getName () + " works the following jobs:\n");
      JobIterator ji = e.getJobIterator ();
      while (ji.hasMoreJobs ())
         System.out.println (ji.nextJob ());
   }
}

JobIterator2 produces the same output as JobIterator1, but the source code differs in that JobIterator2 migrates iterator code from Employee to JobIterator. Also, Employee declares a getJobIterator() method that returns a reference to a new JobIterator object. Notice that JobIterator and Employee are tightly coupled classes: JobIterator's constructor requires a reference to Employee's private jobs array. Keep that coupling in mind, as it offers a clue as to how class nesting works at an internal level (for more information on class nesting internals see the Inner Classes Specification document).

Although JobIterator2 conveniently solves JobIterator1's problems, the new program introduces a new problem: the addition of a new JobIterator class at the same level as Employee prevents the insertion of a future generic JobIterator interface at that same level into the source file. After all, you cannot have two classes/interfaces with the same name at the same level in a source file. Although not a serious problem in our trivial example, in significant programs, situations arise where introducing classes/interfaces with the same names into the same source file is necessary. To make those names coexist, you must recognize that some classes completely depend on other classes. You should be able to declare those dependent classes within the classes they depend on. Listing 3 shows how to declare a dependent JobIterator class within an Employee class -- upon which JobIterator depends:

Listing 3. JobIterator3.java

// JobIterator3.java
class Job
{
   private String jobTitle;
   Job (String jobTitle)
   {
      this.jobTitle = jobTitle;
   }
   public String toString ()
   {
      return jobTitle;
   }
}
class Employee
{
   private String name;
   private Job [] jobs;
   Employee (String name, Job [] jobs)
   {
      this.name = name;
      this.jobs = jobs;
   }
   String getName ()
   {
      return name;
   }
   JobIterator getJobIterator ()
   {
      return new JobIterator ();
   }
   class JobIterator
   {
      private int jobIndex = 0;
      public boolean hasMoreJobs ()
      {
         return jobIndex < jobs.length;
      }
      public Object nextJob ()
      {
         return !hasMoreJobs () ? null : jobs [jobIndex++];
      }
   }
}
class JobIterator3
{
   public static void main (String [] args)
   {
      Job [] jobs = { new Job ("Janitor"), new Job ("Delivery Person") };
      Employee e = new Employee ("John Doe", jobs);
      System.out.println (e.getName () + " works the following jobs:\n");
      Employee.JobIterator eji = e.getJobIterator ();
      while (eji.hasMoreJobs ())
         System.out.println (eji.nextJob ());
   }
}

JobIterator3, which produces the same output as JobIterator1 and JobIterator2, illustrates class nesting: class Employee contains the class JobIterator declaration. That nesting results in JobIterator not requiring a constructor because JobIterator can directly access Employee's private jobs field. Also, that nesting requires JobIterator3's main() method to prefix Employee. to JobIterator whenever main() needs access to JobIterator. Because JobIterator3's JobIterator class no longer requires a constructor or its own jobs field, the source code is somewhat clearer than JobIterator2's source code.

In addition to improved source code clarity, class nesting features a second benefit. When classes nest within other classes, name conflicts reduce. Look carefully at Listing 3. The top-level classes are Job, Employee, and JobIterator3. There is also an Employee.JobIterator class. If we choose to insert a JobIterator interface at the same level as Employee, we would end up with Job, Employee, JobIterator3, and Employee.JobIterator classes, and a JobIterator interface. Because Employee.JobIterator and JobIterator represent two different names, no name conflict results.

Note
If you compile JobIterator3 and examine the resulting class files, you will discover an Employee$JobIterator.class filename. That filename corresponds to the class file containing byte codes for the nested JobIterator class within Employee. Notice the dollar sign character. Java's compiler generates names for nested classes by prefixing their enclosing class names and dollar sign characters to nested class names. Why use a dollar sign? It is a legitimate character that underlying platforms allow for filenames. In contrast, period characters (.) separate the actual name from the file's extension, and are not always valid for use in platform-specific filenames.

What nested classes does Java support?

Java divides nested classes into two main categories: nested top-level classes and inner classes. Furthermore, Java divides inner classes into the instance inner class, local inner class, and anonymous inner class categories. To understand nested classes, you need to understand each category. We begin exploring those categories by focusing on nested top-level classes.

Nested top-level classes

When you declare a class outside any other class, Java regards that class as a top-level class. If you declare a class within a top-level class and prefix keyword static to the nested class declaration, you end up with a nested top-level class. The following code fragment demonstrates a top-level class and a nested top-level class:

class TopLevelClass
{
   static class NestedTopLevelClass
   {
   }
}

Just as a static field and a static method (also known as a class field and a class method) are independent of any objects that you create from their enclosing classes, a nested top-level class is also independent from such objects. Consider the following code fragment:

class TopLevelClass
{
   static int staticField;
   int instanceField;
   static class NestedTopLevelClass
   {
      static
      {
         System.out.println ("Can access staticField " + staticField);
//         System.out.println ("Cannot access instanceField " + instanceField);
      }
      {
         System.out.println ("Can access staticField " + staticField);
//         System.out.println ("Cannot access instanceField " + instanceField);
      }
   }
}

Within the class block and object block initializers of the preceding code fragment's NestedTopLevelClass class, you can access TopLevelClass's staticField variable. However, you cannot access instanceField from within either initializer. Because NestedTopLevelClass cannot access TopLevelClass's instanceField variable, NestedTopLevelClass is independent of any TopLevelClass objects.

Caution
A nested top-level class cannot access any enclosing class's instance members (fields and methods).

Although NestedTopLevelClass cannot access TopLevelClass's instance fields, keyword static does not prevent NestedTopLevelClass from declaring its own instance fields or creating NestedTopLevelClass objects. For proof, check out Listing 4:

Listing 4. NestedTopLevelClassDemo.java

// NestedTopLevelClassDemo.java
class TopLevelClass
{
   static class NestedTopLevelClass
   {
      int myInstanceField;
      NestedTopLevelClass (int i)
      {
         myInstanceField = i;
      }
   }
}
class NestedTopLevelClassDemo
{
   public static void main (String [] args)
   {
      TopLevelClass.NestedTopLevelClass ntlc;
      ntlc = new TopLevelClass.NestedTopLevelClass (5);
      System.out.println (ntlc.myInstanceField);
   }
}

When run, NestedTopLevelClassDemo produces the following output:

5

NestedTopLevelClassDemo's main() method creates a NestedTopLevelClass variable -- ntlc. The syntax for declaring that variable is identical to Listing 3's Employee.JobIterator eji = e.getJobIterator (); syntax. Generally, when you need a nested class type variable, prefix the nested class type's name with the names of all enclosing classes and period separators. The same idea holds when identifying the nested class type after keyword new: you must supply the names of all enclosing classes and period separators prior to the nested class name.

At this point, you might wonder whether you can declare a nested top-level class within a nested top-level class. Also, what happens if two different enclosing classes declare the same field variable name with different types and/or initial values? For an answer to both questions, check out Listing 5:

Listing 5. NestingAndShadowingDemo.java

// NestingAndShadowingDemo.java
class TopLevelClass
{
   private static int a = 1;
   private static int b = 3;
   static class NestedTopLevelClass
   {
      private static int a = 2;
      static class NestedNestedTopLevelClass
      {
         void printFields ()
         {
            System.out.println ("a = " + a);
            System.out.println ("b = " + b);
         }
      }
   }
}
class NestingAndShadowingDemo
{
   public static void main (String [] args)
   {
      TopLevelClass.NestedTopLevelClass.NestedNestedTopLevelClass nntlc;
      nntlc = new TopLevelClass.NestedTopLevelClass.
                                NestedNestedTopLevelClass ();
      nntlc.printFields ();
   }
}

When run, NestingAndShadowingDemo produces the following output:

a = 2
b = 3

The fact that NestingAndShadowingDemo compiles and runs proves that you can nest nested top-level classes within nested top-level classes. Also, the output shows that NestedTopLevelClass's a field shadows TopLevelClass's a field. As a result, the contents of NestedTopLevelClass's a field (2) prints.

Nested top-level classes prove inadequate for accessing an enclosing class's instance fields or calling the class's instance methods. To accommodate instance member access, Java supports inner classes. Inner classes resemble nested top-level classes, except that you never declare an inner class with the static keyword. We now examine inner classes, beginning with the instance inner class category.

Tip
You can declare a nested top-level class with the private, protected, or public keywords to determine that class's level of accessibility to code outside the enclosing class.

Instance inner classes

Suppose you declare a class within another class and omit the static keyword from that nested class's declaration. Instead of ending up with a nested top-level class, you end up with an instance inner class. Unlike nested top-level classes, which can access only enclosing classes' static members, instance inner classes can access both static and instance members. For an example of an instance inner class, refer to Listing 3. Within class Employee, you see instance inner class JobIterator. Look carefully at both classes and you see that JobIterator can access Employee's private jobs instance field.

Tip
You can declare an instance inner class with the private, protected, or public keywords to determine that class's level of accessibility to code outside the enclosing class.

Local inner classes

In addition to allowing class nesting within classes, Java also allows you to nest classes even closer to where they are needed by placing a class in any block, a region of code that appears between open/close brace characters ({ }). That placement implies that the class can appear inside a method declaration and even between a pair of brace characters that follow an if statement. Although the class still nests within some enclosing class, the nested class is now situated much closer to the code that needs to work with that class. Such a class is known as a local inner class.

Local inner classes have one advantage over instance inner classes. In addition to being able to access enclosing classes' instance and class fields (and call the instance and class methods), local inner classes can access local variables and/or a method's parameters. Examine Listing 6 to see a local inner class:

Listing 6. LocalInnerClassDemo.java

// LocalInnerClassDemo.java
import java.util.*;
class ComputerLanguage
{
   private String name;
   ComputerLanguage (String name)
   {
      this.name = name;
   }
   public String toString ()
   {
      return name;
   }
}
class LocalInnerClassDemo
{
   public static void main (String [] args)
   {
      ComputerLanguage [] cl = 
      {
         new ComputerLanguage ("Ada"),
         new ComputerLanguage ("Algol"),
         new ComputerLanguage ("APL"),
         new ComputerLanguage ("Assembly - IBM 360"),
         new ComputerLanguage ("Assembly - Intel"),
         new ComputerLanguage ("Assembly - Mostek"),
         new ComputerLanguage ("Assembly - Motorola"),
         new ComputerLanguage ("Assembly - VAX"),
         new ComputerLanguage ("Assembly - Zilog"),
         new ComputerLanguage ("BASIC"),
         new ComputerLanguage ("C"),
         new ComputerLanguage ("C++"),
         new ComputerLanguage ("Cobol"),
         new ComputerLanguage ("Forth"),
         new ComputerLanguage ("Fortran"),
         new ComputerLanguage ("Java"),
         new ComputerLanguage ("LISP"),
         new ComputerLanguage ("Logo"),
         new ComputerLanguage ("Modula 2"),
         new ComputerLanguage ("Pascal"),
         new ComputerLanguage ("Perl"),
         new ComputerLanguage ("Prolog"),
         new ComputerLanguage ("Snobol")
      };
      Enumeration e = enumerator ((Object []) cl);
      while (e.hasMoreElements ())
         System.out.println (e.nextElement ());
   }
   static Enumeration enumerator (final Object [] array)
   {
      class LocalInnerClass implements Enumeration
      {
         private int index = 0; 
         public boolean hasMoreElements ()
         {
            return index < array.length;
         }
         public Object nextElement ()
         {
            return array [index++].toString ();
         }
      }
      return new LocalInnerClass ();
   }
}

When run, LocalInnerClassDemo produces the following output:

Ada
Algol
APL
Assembly - IBM 360
Assembly - Intel
Assembly - Mostek
Assembly - Motorola
Assembly - VAX
Assembly - Zilog
BASIC
C
C++
Cobol
Forth
Fortran
Java
LISP
Logo
Modula 2
Pascal
Perl
Prolog
Snobol

LocalInnerClassDemo demonstrates the declaration of local inner class LocalInnerClass within LocalInnerClassDemo's enumerator() class method. Notice that enumerator() returns a reference to an object whose class implements the Enumeration interface (located in the java.util package). Because LocalInnerClass also implements Enumeration, enumerator() can legally return a reference to a LocalInnerClass object.

A careful examination of enumerator()'s parameter list reveals a single array parameter of type Object [], and something else -- keyword final. What is the purpose of keyword final? The "Inner Classes Specification" document offers a somewhat cryptic answer:

Because of potential synchronization problems, there is by design no way for two objects to share access to a changeable local variable.

The quote above implies that a nonfinal (that is, changeable) local variable (or parameter), which code within two objects -- a local inner class object and the object whose instance method declares a local inner class -- accesses, can possibly cause synchronization problems.

LocalInnerClassDemo

supplies a

LocalInnerClass

object and the

enumerator()

class method, which both share the nonfinal

array

parameter. This is a different scenario than the quote describes. What kinds of synchronization problems arise? One possibility: Inconsistencies arise when multiple threads attempt simultaneous access to a shared variable. (I discuss this possibility in a future article that explores synchronization.) Another possibility, to which I believe the quote refers: A local inner class modifies a nonfinal local variable, but that modification does not appear later in the method, as the following (illegal) code fragment clarifies:

void someMethod (int x)
{ 
 
   // Assume x contains 20
 
   class Fred
   {
      Fred ()
      {
         x = 30; // Compiler reports error
      }
   }
 
   Fred f = new Fred ();
 
   System.out.println (x); // x still contains 20
 
   return x;
}

The code fragment above introduces the

someMethod()

instance method with a single parameter

x

, which initially contains 20. Local inner class

Fred

's constructor assigns 30 to

x

(which the compiler does not allow, but let's assume otherwise). However,

System.out.println (x);

reveals

x

to contain 20 -- not 30. Talk about confusion! To allow

Fred

access to

x

, the compiler copies

x

within

Fred

.

Fred

's constructor modifies the copy -- not the original

x

. A new and unwary Java developer probably would not know that two copies of

x

exist and expect

x

to contain 30 after

Fred f = new Fred ();

executes. Discovering this incorrect assumption leads to confusion. To avoid this confusion, Sun forces developers to prefix the

int x

parameter declaration with keyword

final

:

void someMethod (final int x)

. As a result,

Fred()

's

x = 30;

statement is illegal.

Note
If the local variable or parameter has primitive type, as opposed to reference type, the compiler simply substitutes a literal for that variable.

Can LocalInnerClassDemo access array after enumerator() returns? After all, doesn't a parameter (final or otherwise) disappear once a method exits? Yes, the parameter disappears; however, if you examine the byte codes in the appropriate class files, you will discover that the compiler performs some sleight-of-hand code generation. First, the compiler creates a LocalInnerClass(Object val$array) constructor in LocalInnerClass. Second, the compiler creates a hidden Object val$array; field within LocalInnerClass. That field is known as a synthetic field because the compiler creates it. Finally, the compiler changes return new LocalInnerClass ();, within enumerator(), to return new LocalInnerClass (array);. Because LocalInnerClass has a copy of the reference to the same Object [] array, as passed to enumerator(), LocalInnerClass's methods can refer to that array long after enumerator()'s array parameter disappears.

Tip
Learn more about the compiler's manipulation of local inner classes into top-level classes by studying the Inner Classes Specification document and by using the javap program to disassemble class files.

Anonymous inner classes

When a class is short, you'll want to declare a local inner class without a name because that name won't contribute any meaning to the class, Also, by not choosing a name, you reduce the chance of name conflicts when several local inner classes appear within the same enclosing class. A local inner class that lacks a name is known as an anonymous inner class.

Because an anonymous inner class lacks a name, you declare that class where you create an object from that class, as Listing 7 demonstrates:

Listing 7. AnonymousInnerClassDemo1.java

// AnonymousInnerClassDemo1.java
abstract class Farmer
{
   protected String name;
   Farmer (String name)
   {
      this.name = name;
   }
   abstract void occupation ();
}
class BeefFarmer extends Farmer
{
   BeefFarmer (String name)
   {
      super (name);
   }
   void occupation ()
   {
      System.out.println ("Farmer " + name + " raises beef cattle");
   }
}
class AnonymousInnerClassDemo1
{
   public static void main (String [] args)
   {
      BeefFarmer bf = new BeefFarmer ("John Doe");
      bf.occupation ();
      new Farmer ("Jane Doe")
          {
              void occupation ()
              {
                 System.out.println ("Farmer " + name + " milks cows");
              }
          }.occupation ();
   }
}

When run, AnonymousInnerClassDemo1 produces the following output:

Farmer John Doe raises beef cattle
Farmer Jane Doe milks cows

AnonymousInnerClassDemo1 declares an abstract Farmer class that encapsulates a farmer's name and occupation -- via an abstract occupation() method. A BeefFarmer class extends Farmer and overrides occupation() to identify what a beef farmer does. That class appears in AnonymousInnerClassDemo1's main() method, which creates a BeefFarmer object and calls its occupation() method to print out the beef farmer's occupation. No surprises here! However, as you continue to examine main(), you encounter something strange: new Farmer ("Jane Doe") { ... }. It appears that we are trying to create an object from the abstract Farmer class, which is impossible. But what does that open brace character following ("Jane Doe") mean? Surely that is not legal Java code? In actual fact, the code is legal and reads like this: "Have the JVM create an object from a Farmer anonymous subclass." That subclass overrides Farmer's occupation() method and calls the Farmer (String name) constructor to initialize Farmer's protected name field. After creating that object, call its occupation() method and then discard the object's reference so the object becomes eligible for garbage collection.

AnonymousInnerClassDemo1's use of a superclass constructor to initialize the resulting object's superclass layer begs the following question: can I declare my own constructors in an anonymous class? The answer is no. Because a constructor requires a class name and because an anonymous class has no name, how could the compiler choose a name?

Caution
Attempts to declare constructors in an anonymous inner class fail because constructors must have the names of the classes in which they appear and anonymous inner classes have no names.

Instead of constructors, you can use an object block initializer to perform custom initialization when creating an object from an anonymous inner class. For example, suppose you want to customize the Farmer Jane Doe milks cows message in AnonymousInnerClassDemo1's anonymous subclass of Farmer. You want to pass the number of cows to be milked on the command line and have that value appear in the message. Because an object block initializer executes during object creation, you simply perform the appropriate command line argument initialization in an object block initializer, as Listing 8 demonstrates:

Listing 8. AnonymousInnerClassDemo2.java

// AnonymousInnerClassDemo2.java
abstract class Farmer
{
   protected String name;
   Farmer (String name)
   {
      this.name = name;
   }
   abstract void occupation ();
}
class BeefFarmer extends Farmer
{
   BeefFarmer (String name)
   {
      super (name);
   }
   void occupation ()
   {
      System.out.println ("Farmer " + name + " raises beef cattle");
   }
}
class AnonymousInnerClassDemo2
{
   public static void main (final String [] args)
   {
      BeefFarmer bf = new BeefFarmer ("John Doe");
      bf.occupation ();
      new Farmer ("Jane Doe")
          {
              private String count;
              {
                 if (args.length == 1)
                     count = args [0];
              }
              void occupation ()
              {
                 if (count == null)
                     System.out.println ("Farmer " + name + " milks cows");
                 else
                     System.out.println ("Farmer " + name + " milks " +
                                         count + " cows");
              }
          }.occupation ();
   }
}

Assuming you type java AnonymousInnerClassDemo2 10 at the command line, you receive the following output:

Farmer John Doe raises beef cattle
Farmer Jane Doe milks 10 cows

AnonymousInnerClassDemo2 still initializes the resulting object's Farmer layer, by calling constructor Farmer (String name) with Jane Doe as the object value that name references. However, the resulting object's anonymous layer also has a chance to initialize, through the object block initializer.

Note
Even though an anonymous inner class has no name, the compiler still needs to generate a name for a class file. It turns out that the compiler chooses integer numbers, which append to enclosing class names and dollar sign characters, for anonymous inner class names. For example, in AnonymousInnerClassDemo2, the compiler generates AnonymousInnerClassDemo2.class as the class file name for the anonymous inner class.

Before you leave this section, consider the following practical application of anonymous inner classes: Developers often use anonymous inner classes to simplify event-handling -- that is, notifications of significant activities, such as moving the mouse or pressing a key -- in programs that generate and display graphical user interfaces (GUIs). Using an anonymous inner class for event handlers proves convenient because they often do not require class names. Listing 9 presents an example where an anonymous inner class facilitates the handling of the window-closing event:

Listing 9. AnonymousInnerClassDemo3.java

// AnonymousInnerClassDemo3.java
import java.awt.*;
import java.awt.event.*;
class AnonymousInnerClassDemo3
{
   public static void main (String [] args)
   {
      // Create a rectangular frame window with a title bar at the top.
      Frame f = new Frame ("Anonymous Inner Class Demo #3");
      // Add a window listener that will generate a window closing event
      // in response to user attempts to click the little X button (on
      // Windows platforms) to the right of the title bar. When the user
      // clicks that button, the window closing event results in a call
      // to a method named windowClosing().  By calling System.exit (0);
      // from within that method, the application exits.
      f.addWindowListener (new WindowAdapter ()
                           {
                               public void windowClosing (WindowEvent e)
                               {
                                  System.exit (0);
                               }
                           });
      // Establish the frame window's size as 300 horizontal pixels by
      // 100 vertical pixels.
      f.setSize (300, 100);
      // Display the frame window and get the underlying event handling
      // system running.
      f.setVisible (true);
   }
}

When run, AnonymousInnerClassDemo3 displays a rectangular window, known as a frame window, because it is the resulting GUI's main window. Once the frame window appears, however, the user should be able to remove that window and stop AnonymousInnerClassDemo3's execution. That termination typically occurs in response to the user clicking a little button, labeled X, to the top right of the window's title bar -- at least on Windows platforms.

When the user clicks X, the underlying Java windowing toolkit creates an event object and calls a special method -- windowClosing (WindowEvent e), where e contains a reference to that event object -- in an object known as the frame window's window listener. That listener object registers with the underlying toolkit (so the toolkit knows the method location) by calling Frame's addWindowListener (WindowListener wl) method.

The WindowListener interface declares several methods, one method for each potential window event. To spare the developer from having to supply code for all those methods, Java's windowing toolkit designers built a WindowAdapter class that implements all WindowListener methods. Because those implemented methods are empty stubs, the developer declares an anonymous inner class that extends WindowAdapter and overrides one or more of those methods with specific code. As you can see, I have chosen to override windowClosing (WindowEvent e) and have that method call System.exit (0); to terminate the program (which also closes the window), in response to a call made by the windowing toolkit to windowClosing (WindowEvent e).

Note
You will often work with anonymous inner classes when developing event handlers for your GUIs. In future articles, I will provide many examples of event handlers built from anonymous inner classes.

Review

This article has shown class nesting to be a useful addition to the Java language. Not only does class nesting help clarify source code -- because you can declare classes closer to the objects they manipulate -- it also helps reduce the number of conflicts between the names of classes declared at the same level within a source file.

There are four categories of nested classes: nested top-level classes, instance inner classes, local inner classes, and anonymous inner classes. Nested top-level classes can only access the class fields and call the class methods in any enclosing class. In contrast, instance inner classes can access an enclosing class's class and instance fields, and call an enclosing class's class or instance methods. Because nested top-level classes and instance inner classes can only appear within the confines of another class, Java supplies local inner classes that can appear within any block -- including a method block or the block following an if statement. Furthermore, because some local inner classes are so short they do not warrant their own class names, Java supplies anonymous inner classes. Local and anonymous inner classes can access the local variables and parameters accessible within their enclosing blocks, provided that you mark those local variables and parameters final.

I encourage you to email me with any questions you might have involving either this or any previous article's material. (Please, keep such questions relevant to material discussed in this column's articles.) Your questions and my answers will appear in the relevant study guides.

In next month's article, you will learn about exceptions and how to handle them.

Jeff Friesen has been involved with computers for the past 20 years. He holds a degree in computer science and has worked with many computer languages. Jeff has also taught introductory Java programming at the college level. In addition to writing for JavaWorld, he has written his own Java book for beginners -- Java 2 By Example, Second Edition (Que Publishing, 2001; ISBN: 0789725932) -- and helped write Special Edition Using Java 2 Platform (Que Publishing, 2001; ISBN: 0789724685). Jeff goes by the nickname Java Jeff (or JavaJeff). To see what he's working on, check out his Website at http://www.javajeff.com.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more