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 oracle.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 oracle.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 oracle.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 oracle.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 tutorial'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.

Screenshot

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 oracle.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 oracle.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 oracle.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.

      
Comments