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

advertisement

AddThis Social Bookmark Button

Nested Classes, Part 2
Pages: 1, 2

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 applications, 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 oreilly.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 Chapter 1).

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 oreilly.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 programming 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.

In next week's concluding installment to this series on nested classes, excerpted from Hardcore Java, author Robert Simmons will cover static nested classes, double nested classes, and nested classes in interfaces.


Return to ONJava.com.