Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Classes within classes

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

  • Print
  • Feedback

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.

  • Print
  • Feedback

Resources