Limited-Scope Inner Classes

One of the strangest types of inner classes is the limited-scoped inner class. Limited-scoped classes are scoped to a particular block of code. Their declaration and usage all happen within that block. To get a better idea of how limited-scoped inner classes work, see Example 6-4.

Example 6-4. A limited-scope inner class scoped to a method
package oracle.hcj.nested;
public class MethodInnerClassDemo extends JDialog {
 /** Holds the location of the logo image. */
 private static final String LOGO_LOCATION =
 "oracle/hcj/nested/oracle_header3.gif";
 static {
 LOGO = new ImageIcon(ClassLoader.getSystemClassLoader( )
 .getResource(LOGO_LOCATION));
 }
 /** Holds a reference to the content pane. */
 private final Container contentPane;
 /** holds a demo variable. */
 private String demo;
 public MethodInnerClassDemo(final int value) {
 super( );
 String title = "Inner Class Demo";
 setTitle(title);
 setModal(true);
 contentPane = getContentPane( );
 contentPane.setLayout(new BorderLayout( ));
 JLabel logoLabel = new JLabel(LOGO);
 contentPane.add(BorderLayout.NORTH, logoLabel);
 JButton btn = new JButton("Beep");
 class MyActionListener implements ActionListener {
 public void actionPerformed(final ActionEvent event) {
 Toolkit.getDefaultToolkit( ).beep( );
 System.out.println(value);
 System.out.println(LOGO_LOCATION);
 System.out.println(MethodInnerClassDemo.this.demo);
 }
 }
 btn.addActionListener(new MyActionListener( ));
 contentPane.add(BorderLayout.SOUTH, btn);
 pack( );
 }
}


Other than the fact that the declaration of the class occurs within the body of a method, this looks like any other class declaration. You can even use the keywords final and abstract on these declarations and develop class hierarchies all within one limited block. Once declared, you can use the class throughout the remainder of the method just like any other class. However, there are some important differences between limited-scope inner classes and normal classes. First of all, limited-scope inner classes can access final variables of the declaring block. In Example 6-4, the actionPerformed( ) method of MyActionListener uses the variable value despite the fact that it hasn't declared this variable. In this case, value is a final parameter to the method; therefore, MyActionListener can access it. However, MyActionListener would not have access to the local variable title, as it is not final. In addition to final variables of the enclosing block, a limited-scope inner class can access all members of the enclosing class, including the private variables. In Example 6-4, the LOGO_LOCATION and demo variables are class members; therefore, MyActionListener can use them within its methods despite the fact that both are private. Note especially the syntax used to access the demo variable in the outer declaring class:

System.out.println(MethodInnerClassDemo.this.demo);


This is the proper way to access an instance variable of an outer class from an inner class. The syntax specifies which this pointer to use, which is critical when your inner class has a variable with the same name as a variable in the outer class. If you use this syntax without the type specifier, you will get access only to the variable in the inner class. Additionally, the limited-scope inner classes are subject to scoping rules that are unique to these classes. Example 6-5 shows a class that has several limited-scope inner classes.

Example 6-5. Various limited-scope inner classes
package oracle.hcj.nested;
public class MethodInnerClassDemo extends JDialog {
 public MethodInnerClassDemo(final int value) {
 super( );
 String title = "Inner Class Demo";
 setTitle(title);
 setModal(true);
 contentPane = getContentPane( );
 contentPane.setLayout(new BorderLayout( ));
 JLabel logoLabel = new JLabel(LOGO);
 contentPane.add(BorderLayout.NORTH, logoLabel);
 JButton btn = new JButton("Beep");
 class MyActionListener implements ActionListener {
 public void actionPerformed(final ActionEvent event) {
 Toolkit.getDefaultToolkit( ).beep( );
 System.out.println(value);
 System.out.println(LOGO_LOCATION);
 System.out.println(MethodInnerClassDemo.this.demo);
 // System.out.println(title); // <= compiler error
 }
 }
 btn.addActionListener(new MyActionListener( ));
 contentPane.add(BorderLayout.SOUTH, btn);
 pack( );
 }
 public MethodInnerClassDemo( ) {
 super( );
 setTitle("Inner Class Demo");
 setModal(true);
 contentPane = getContentPane( );
 contentPane.setLayout(new BorderLayout( ));
 JLabel logoLabel = new JLabel(LOGO);
 contentPane.add(BorderLayout.NORTH, logoLabel);
 JButton btn1 = new JButton("Beep");
 JButton btn2 = new JButton("Bell");
 class MyActionListener implements ActionListener {
 public void actionPerformed(final ActionEvent event) {
 Toolkit.getDefaultToolkit( ).beep( );
 }
 }
 btn1.addActionListener(new MyActionListener( ));
 btn2.addActionListener(new MyActionListener( ));
 JPanel pnl = new JPanel(new GridLayout(1, 2));
 pnl.add(btn1);
 pnl.add(btn2);
 contentPane.add(BorderLayout.SOUTH, pnl);
 pack( );
 }
 public void someMethod( ) {
 ActionListener listener = 
 new MyActionListener( ); // <= compiler error.
 }
}


In Example 6-5, you declare two different limited-scope inner classes in two different constructors. Each of these classes is in scope only within the method in which it is declared. Therefore, the MyActionListener class in the first constructor is a different class than the one in the second constructor despite the fact that they both have the same name. Furthermore, attempting to use one of the MyActionListener classes outside the method, which is done in someMethod( ), will result in a compiler error—specifically, an undeclared type error. These classes don't necessarily need to be scoped to a method. You could have surrounded them with curly braces, thereby scoping them to a particular block. In fact, many Java GUIs employ a specific type of limited-scope inner class, which is scoped only to a specific method call or assignment. These classes are called anonymous classes.

Anonymous Classes

The concept of anonymous classes often confuses Java developers. Essentially, anonymous classes are limited-scope classes that are declared but are not given a name by the programmer (hence the designation "anonymous") and have only a limited lifespan. Although they can be used for a wide variety of apps, they are most commonly used as event handlers in GUI programs. A common example of an anonymous class is shown in Example 6-6.

Example 6-6. An anonymous class
package oracle.hcj.nested;
public class AnonymousDemo extends JDialog {
 public AnonymousDemo( ) {
 super( );
 setTitle("Anonymous Demo");
 setModal(true);
 contentPane = getContentPane( );
 contentPane.setLayout(new BorderLayout( ));
 JLabel logoLabel = new JLabel(LOGO);
 contentPane.add(BorderLayout.NORTH, logoLabel);
 JButton btn = new JButton("Beep");
 btn.addActionListener(new ActionListener( ) {
 public void actionPerformed(final ActionEvent event) {
 Toolkit.getDefaultToolkit( ).beep( );
 }
 });
 contentPane.add(BorderLayout.SOUTH, btn);
 pack( );
 }
}


This example declares an anonymous class that listens to and acts on the action events produced by a button. Because the syntax of the anonymous class is a little peculiar, it is worth studying in detail.

Anonymous class syntax

Anonymous classes can be used only once, so they must be used as a parameter to a method or assigned to a variable. Consequently, it is necessary to declare and institute the class in one move, which is why you start out using the keyword new to instantiate the class you are declaring. In this case, the anonymous class is declared, instantiated, and then passed to the addActionListener( ) method of the btn object:

btn.addActionListener(new ActionListener( ) {
 public void actionPerformed(final ActionEvent event) {
 Toolkit.getDefaultToolkit( ).beep( );
 }
});


The declaration of the anonymous class itself starts with the class name of the supertype of the anonymous class. In this case, you use the interface ActionListener as the supertype:

btn.addActionListener(new ActionListener( ) {
 public void actionPerformed(final ActionEvent event) {
 Toolkit.getDefaultToolkit( ).beep( );
 }
});


Anonymous classes can use any non-final interface or class as their supertype. If you use an interface, then the superclass of the anonymous class will automatically be java.lang.Object (see ). The parameters to the superclass constructor immediately follow the supertype. This allows you to pass arguments to the constructor of the superclass of your anonymous class. If there are multiple constructors to the superclass, you can pass parameters that are appropriate for any one of those constructors. In this case, the supertype of your anonymous class is an interface, and the superclass is java.lang.Object. Therefore, you don't need to pass any arguments to the superclass, and the parentheses are empty.

btn.addActionListener(new ActionListener( ) {
 public void actionPerformed(final ActionEvent event) {
 Toolkit.getDefaultToolkit( ).beep( );
 }
});


By contrast, here is an anonymous class declaration in which the programmer passed arguments to the constructor:

JButton btn = new JButton("Beep") {
 // . . . contents omitted
};


In this case, the supertype of the anonymous class is javax.swing.JButton. The JButton class has several constructors; however, the programmer chose to pass arguments to the constructor that takes a single String parameter. One difference between anonymous classes and limited-scope classes is that anonymous classes cannot use the keywords abstract or final. In fact, every anonymous class is considered to be final by default. When combined with the fact that these classes have no name, this means that it is impossible to declare hierarchies of anonymous classes. The declaration of the contents of the class follows the parameters to the supertype constructor:

btn.addActionListener(new ActionListener( ) {
 public void actionPerformed(final ActionEvent event) {
 Toolkit.getDefaultToolkit( ).beep( );
 }
});


In the example, you are declaring a class that implements ActionListener, so you have to override the actionPerformed( ) method in your anonymous class, just as if you had been writing a normal implementation class. If you want, you can also place other code within the body. The only thing that you can't place within an anonymous class is a constructor. In fact, it would be impossible to declare a constructor because constructors use the name of the class as the method name, and an anonymous class doesn't have a programmer-accessible type name. The syntax for anonymous classes is a little weird and is not considered to be mainstream Java syntax. However, examining how the compiler deals with a declaration of your anonymous class will help you understand how it works. Here, the compiler views your anonymous class from Example 6-4:

public AnonymousDemo( ) {
 // . . . snip
 class AnonymousDemo$1 implements ActionListener {
 public AnonymousDemo$1 ( ) {
 }
 public void actionPerformed(final ActionEvent event) {
 Toolkit.getDefaultToolkit( ).beep( );
 }
 }
 // . . . snip
}


When the compiler sees an anonymous class declaration, the compiler creates a unique identifier for the anonymous class and then declares it as a normal method-scoped inner class. The identifier that the compiler uses for your anonymous class is the position of the anonymous class in the order declared in the source file. In the case of the above example, you know that this anonymous class is the first one declared when reading from top to bottom in the source file. However, you can never depend on this name, so you can't reuse the class by instantiating it. After the name declaration, the compiler adds the inheritance structure for the class. If ActionListener had been a class instead of an interface, the compiler would have used the keyword extends instead of implements. Finally, the compiler adds the body of the anonymous class to the newly declared class. Once the class is declared, the virtual machine uses the new class in the method:

 public AnonymousDemo(final in) {
 super( );
 setTitle("Anonymous Demo");
 setModal(true);
 contentPane = getContentPane( );
 contentPane.setLayout(new BorderLayout( ));
 JLabel logoLabel = new JLabel(LOGO);
 contentPane.add(BorderLayout.NORTH, logoLabel);
 JButton btn = new JButton("Beep");
 class AnonymousDemo$1 implements ActionListener {
 public AnonymousDemo$1 ( ) {
 }
 public void actionPerformed(final ActionEvent event) {
 Toolkit.getDefaultToolkit( ).beep( );
 }
 }
 btn.addActionListener(new AnonymousDemo$1( ));
 contentPane.add(BorderLayout.SOUTH, btn);
 pack( );
 }


In this example, the newly declared class is replaced in the source code with the expansion. Then the addActionListener( ) method line declares a new instance of this class to pass as a parameter. Although you don't have to know all of these details to use anonymous classes, it helps to track down bugs and understand the Java compiler—which are both important for hardcore Java programmers.

Problems with Limited-Scope Inner Classes

Since the syntax of anonymous classes is not considered to be mainstream Java syntax, anonymous classes are difficult to read and are often misunderstood. Furthermore, they tend to be overused, especially in GUI code. Example 6-7 shows a syntax that is similar to one I encountered in a GUI for a banking system.

Example 6-7. Anonymous classes galore
package oracle.hcj.nested;
public class AnonymousDemo extends JDialog {
 public AnonymousDemo(final int exitDelay) {
 super( );
 setTitle("Anonymous Demo");
 setModal(true);
 contentPane = getContentPane( );
 contentPane.setLayout(new BorderLayout( ));
 final String delayDisplay =
 new Object( ) {
 public String toString( ) {
 System.out.println(demo);
 if ((exitDelay) > 1000) {
 // Show in seconds. 
 NumberFormat formatter = NumberFormat.getNumberInstance( );
 formatter.setMinimumFractionDigits(2);
 double time = exitDelay / 1000.0;
 return (new String(formatter.format(time) + " seconds"));
 } else {
 // Show in Microseconds
 return new String(exitDelay + " microseconds");
 }
 }
 }.toString( );
 addWindowListener(new WindowAdapter( ) {
 public void windowClosing(final WindowEvent event) {
 try {
 System.out.println("Waiting for " + delayDisplay);
 Thread.sleep(exitDelay);
 } catch (final InterruptedException ex) {
 throw new RuntimeException(ex);
 }
 }
 });
 JLabel logoLabel = new JLabel(LOGO);
 contentPane.add(BorderLayout.NORTH, logoLabel);
 JButton btn =
 new JButton("Beep") {
 public void fireActionPerformed(final ActionEvent event) {
 if (LOGGER.isDebugEnabled( )) {
 LOGGER.debug(event);
 LOGGER.debug("This class is: " + this.getClass( ).getName( ));
 }
 super.fireActionPerformed(event);
 }
 };
 btn.addActionListener(new ActionListener( ) {
 public void actionPerformed(final ActionEvent event) {
 doBeep( );
 }
 });
 contentPane.add(BorderLayout.SOUTH, btn);
 pack( );
 }
}


In this single method, the programmer declared no less than four anonymous classes, all for different reasons. The code in Example 6-7 is a textbook demonstration of anonymous classes. However, anyone that writes code like this should probably be beaten to death with a code-readability guide. It's just far too difficult to comprehend at a glance. The result achieved by the anonymous classes can be accomplished by implementing interfaces and proper method calls with much more clarity and maintainability. For example, the class itself could implement ActionListener and WindowListener to provide the event handling. Furthermore, the construction of the delayDisplay would have been easier to read if it had been in a method call instead. Even though the source code would have been longer, the maintenance situation would be far easier to understand. Other limited-scope inner classes suffer from similar readability issues. When you embed a class declaration inside of a method block, you can easily confuse the reader and increase the time it takes to integrate new developers into a project. Additionally, anonymous classes are not reusable at all. This goes against why you declare a class in the first place. The core of object-oriented coding is reusability; therefore, declaring a nonreusable class violates the principles of object-oriented engineering. In many GUIs, I've noticed instances in which programmers declare several anonymous classes that are nearly identical. Although this will work, it is not very object-oriented. Finally, anonymous and other limited-scope inner classes make debugging code difficult and confusing. You could be stepping through code with a debugger and suddenly be transported to a piece of code in the middle of a method. It would require concentration and code awareness to realize that you are in a class declaration. Also, since anonymous classes don't have programmer-accessible names, errors that occur in that class are often difficult to locate. All of these problems make limited-scope inner classes problematic. Therefore, I advise you to avoid them whenever possible. However, you should now be prepared when you encounter them in someone else's code. If you have a chance, I would recommend that you convert their code to use other techniques instead.

      
Comments