Java 101: Classes within classes

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

1 2 3 Page 2
Page 2 of 3
// 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.

1 2 3 Page 2
Page 2 of 3