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.
-
Are limited-scope inner classes necessary?
2004-05-31 14:21:45 Behrang [View]
-
Are limited-scope inner classes necessary?
2004-05-31 14:25:27 Behrang [View]
- Trackback from http://www.metzener.com/chalkboard/index.php/chalkboard/onjavacom_nested_classes/
ONJava.com: Nested Classes
2004-05-31 07:31:12 [View]