ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button O'Reilly Book Excerpts: Hardcore Java

Nested Classes, Part 1

Related Reading

Hardcore Java
By Robert Simmons, Jr.

by Robert Simmons, Jr.

Author's note: The use of nested classes in Java is a constant source of confusion for many programmers. Yet understanding nested classes is critical to such things as the proper implementation of UML composition relationships in Java. In addition, such understanding will help the professional developer unravel spaghetti code put out by other developers.

In this first excerpt in a three-part series of excerpts from Chapter 6 ("Nested Classes") of my book, I cover the first of the three basic categories of nested classes--inner classes. Over the next two weeks I will cover limited-scope inner classes and static nested classes.

This chapter was actually one of the last I wrote for Hardcore Java. I was inspired to write it after spending a long day with a junior developer explaining the concept of nested classes. Furthermore, I had read other texts that referred to static nested classes as inner classes (which is incorrect). As I researched the issue more and more, it became clear that these classes were overwhelmingly poorly understood. Questions about nested classes are among the most prevalent on the Java Developer Connection forums, and the tutorials on Java that I was able to find only glossed over the issues. I wasn't able to find a single Java text that discussed them in detail. All these factors inevitably led to the decision to include a chapter on the subject in Hardcore Java.

While writing the chapter, I used many code examples to test various concepts and to fully explore the concept of nested classes in detail. In fact, I can safely say that I learned as much writing the chapter as I hope you will learn reading it. My goal was to make the most complete description of nested classes to be found anywhere.

The concept of nesting a class within another class or method presents unique issues not found elsewhere in object-oriented programming. Not all types of nested classes should be used routinely; however, you will likely encounter most of them in other people’s code. Therefore, it is important that you understand how the various nested classes function.

Nested classes fall into one of three basic categories:

  • Inner classes
  • Limited-scope inner classes
  • Static nested classes

Each of these categories has its own access rules and usage. Let's take a look at the first--inner classes.

Inner Classes

Inner classes are fairly common within the JDK, especially in collections. Example 6-1 shows a class-scoped inner class.

Example 6-1. A class-scoped inner class

package oreilly.hcj.nested;
public class InnerClassDemo extends JDialog {
   public InnerClassDemo(final int beepCount) {
      super( );
      setTitle("Anonymous Demo");
      contentPane = getContentPane( );
      contentPane.setLayout(new BorderLayout( ));
	  
      JLabel logoLabel = new JLabel(LOGO);
      contentPane.add(BorderLayout.NORTH, logoLabel);
      JButton btn = new BeepButton("Beep");
      contentPane.add(BorderLayout.SOUTH, btn);
      pack( );
      this.beepCount = beepCount;
   }
   
   private class BeepButton extends JButton implements ActionListener {
      public BeepButton(final String text) {
         super(text);
         addActionListener(this);
      }

      public void actionPerformed(final ActionEvent event) {
         try {
            for (int count = 0; count < beepCount; count++) {
               Toolkit.getDefaultToolkit().beep( );
               Thread.sleep(100); // wait for the old beep to finish.
            }
         } catch (final InterruptedException ex) {
            throw new RuntimeException(ex);
         }
      }
   }
}

This code shows how inner classes are used in a GUI dialog. To create a special kind of button that beeps when you click on it, the javax.swing.JButton class has been extended and the appropriate action listener has been added. In the constructor, another of these special buttons is instantiated and added to the dialog. Although this seems pretty simple, there are a couple of interesting points worthy of study.

First of all, you may notice that the variable beepCount, used in the for statement,is in scope inside the action listener, even though it was not declared in the actionPerformed( ) method. This is because inner classes can access all members of the declaring class, even private members. In fact, the inner class itself is said to be a member of the class; therefore, following the rules of object-oriented engineering, it should have access to all members of the class.

The other interesting point is the visibility keyword on the class declaration line:

private class BeepButton extends JButton implements ActionListener {

Because this class is declared as private, other classes can't instantiate this class. This restriction behaves as public, protected, and private visibility keywords behave for any other member of the enclosing class.

Using public visibility with inner classes leads to some rather strange syntax. To demonstrate,let's create an example of a public inner class and then use it. The inner class is shown here:

// From oreilly.hcj.nested.MonitorScreen
public class MonitorScreen {
   
   public class PixelPoint {
      private int x;
      private int y;

      public PixelPoint(final int x, final int y) {
         this.x = x;
         this.y = y;
      }
	  
      public int getX( ) {
         return x;
      }
	  
      public int getY( ) {
         return y;
      }
   }
}

In this example, an inner class represents a pixel on your screen. Since the inner class is public, you can create an instance of PixelPoint outside of the enclosing class. Your first attempt may look something like the following:

package oreilly.hcj.nested;
public class PublicInnerClassAccess {
   public static final void main(final String[] args) {
      MonitorScreen.PixelPoint obj =
      new MonitorScreen.PixelPoint( ); // <= Compiler error.
   }
}

The problem here is that the scope of the inner class is restricted only to an instance of the enclosing class. For this reason, such a static declaration won't work. You need an instance of the class to complete the construction. This can be accomplished with the following code:

package oreilly.hcj.nested;
public class PublicInnerClassAccess {
   public static final void main(final String[] args) {
      MonitorScreen screen = new MonitorScreen( );
      MonitorScreen.PixelPoint pixel = screen.new PixelPoint(25, 40);
      System.out.println(pixel.getX( ));
      System.out.println(pixel.getY( ));
   }
}

In this version,you create a new instance of MonitorScreen and use it to create a new instance of PixelPoint with the following code:

screen.new PixelPoint(25, 40);

The syntax of the creation looks strange, but it really does work, as you can see in this book's sample code. That being said, I don't recommend you use this sort of syntax routinely, as it is confusing to read and violates the object-oriented engineering principles behind inner classes.

Inner classes are best used to represent composition relationships in an object model. Therefore, the enclosing instances should always have total control over the enclosed instances. In this case, a PixelPoint cannot exist outside of a monitor screen, so composition is indicated.

Note: If you recall, composition models aggregation in which the life cycle of the objects being aggregated are under the control of the object performing the aggregation. If the object performing the aggregation goes out of scope, so should the objects it is composed of. The decision to use composition instead of aggregation comes down to a question of whether the composed class can exist on its own. If it can't, then composition is indicated; otherwise, aggregation is indicated.

Since inner classes are best used to model composition, I strongly suggest you stick to private and protected visibility for your inner classes.

Hierarchies of Inner Classes

Like normal classes, inner classes can exist in a hierarchy. In fact, inner classes can even carry the hierarchy restrictions of abstract and final. To understand these hierarchies, study the abstract inner class in Example 6-2.

Example 6-2. A basic monitor screen class

package oreilly.hcj.nested;
public abstract class BasicMonitorScreen {
   private Dimension resolution;
   
   public BasicMonitorScreen(final Dimension resolution) {
      this.resolution = resolution;
   }
   
   public Dimension getResolution( ) {
      return this.resolution;
   }
   
   protected abstract class PixelPoint {
      private int x;
	  
      private int y;
      
	  public PixelPoint(final int x, final int y) {
         this.x = x;
         this.y = y;
      }
      
	  public int getX( ) {
         return x;
      }
      
	  public int getY( ) {
         return y;
      }
   }
}

The BasicMonitorScreen class provides the base class for all monitor screens in your overall class hierarchy. Since the screen is composed of pixels,which indicates composition, the PixelPoint class is declared as an inner class.

Note that both the enclosing class and the inner class are declared abstract. However, there is no connection at all between the two. The hierarchies for the enclosing class and inner class should be considered independently. In the example, the BasicMonitorClass is said to be abstract because it can't exist without more specificity. The PixelPoint class also has the same dynamics, but it could have been declared concrete and simply used by subclasses. In this case, however, the subclasses have to define a concrete subclass of PixelPoint to use it. Also, make sure that an abstract inner class doesn't make the enclosing class abstract, as with an abstract method.

Creating concrete classes within an abstract base class is a good way to model composition relationships in an inheritance hierarchy. You can declare the composed classes in the base class and let the subclasses use that composed class.

In this situation, you need to create a concrete type of PixelPoint to use it. The subclasses are shown in Example 6-3.

Example 6-3. A class for a color monitor

package oreilly.hcj.nested;
public class ColorMonitorScreen extends BasicMonitorScreen {
   public ColorMonitorScreen(final Dimension resolution) {
      super(resolution);
   }

   protected class ColorPixelPoint extends PixelPoint {
      private Color color;
      public ColorPixelPoint(final int x, final int y, final Color color) {
         super(x, y);
         this.color = color;
      }
	  
      public Color getColor( ) {
         return this.color;
      }
   }
}

In this example,you create inheritance hierarchies for both the enclosing and inner classes. Now you have an inner class that can be instantiated in the virtual machine. However, watch out for one little gotcha with inherited inner classes:

package oreilly.hcj.nested;
public class ColorMonitorScreen extends BasicMonitorScreen {
   protected class ColorPixelPoint extends PixelPoint {
      public void someMethod( ) {
         System.out.println(resoultion); // <= compiler error
      }
   }
}

Remembering that inner classes can access the members of their enclosing instance, try to access BasicMonitorScreen's resolution class variable in your ColorPixelPoint inner class. The compiler won't let you access resolution because the special privileged access to the enclosing class's members apply only to the inner classes declared in the enclosing class. This naturally follows since these inner classes are members of the enclosing class, but subclasses of them declared outside the enclosing class are not members. Since ColorPixelPoint is not a member of BasicMonitorScreen, access to BasicMonitorScreen's private and protected members is not allowed.

Finally, keep in mind that public inner classes can also be extended by classes that are not inner classes. However, since public inner classes are generally a bad idea, you shouldn't be doing this anyway. If you have an overwhelming compulsion to employ this technique, you should consider using static nested classes instead.

Robert Simmons, Jr. lives and works as a senior software architect in Germany. He is the author of O'Reilly's Hardcore Java.


Return to ONJava.com.