46. Adding nested classes in anonymous classes
In the previous problem, we had a brief overview of nested classes. As a quick reminder, an anonymous class (or, anonymous inner class) is like a local inner class without a name. Their purpose is to provide a more concise and expressive code. However, the code readability may be affected (look ugly), but it may be worth it if you can perform some specific task without having to do a full-blown class. For instance, an anonymous class is useful for altering the behavior of an existing method without spinning a new class. Java uses them typically for event handling and listeners (in GUI applications). Probably the most famous example of an anonymous class is this one from Java code:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
...
}
}
Nevertheless, while local inner classes are actually class declarations, anonymous classes are expressions. To create an anonymous class, we have to extend an existing class or implement an interface, as shown in the following figure:
Figure 2.29: Anonymous class via class extension and interface implementation
Because they don’t have names, anonymous classes must be declared and instantiated in a single expression. The resulting instance can be assigned to a variable that can be referred to later. The standard syntax for expressions looks like calling a regular Java constructor having the class in a code block ending with a semi-colon (;
). The presence of a semi-colon is a hint that an anonymous class is an expression that must be part of a statement.
Finally, anonymous classes cannot have explicit constructors, be abstract, have a single instance, implement multiple interfaces, or be extended.
Next, let’s tackle a few examples of nesting classes in anonymous classes. For instance, let’s consider the following interface of a printing service:
public interface Printer {
public void print(String quality);
}
We use the Printer
interface all over the place in our printing service, but we also want to have a helper method that is compact and simply tests our printer functions without requiring further actions or an extra class. We decided to hide this code in a static method named printerTest()
, as follows:
public static void printerTest() {
Printer printer = new Printer() {
@Override
public void print(String quality) {
if ("best".equals(quality)) {
Tools tools = new Tools();
tools.enableLaserGuidance();
tools.setHighResolution();
}
System.out.println("Printing photo-test ...");
}
class Tools {
private void enableLaserGuidance() {
System.out.println("Adding laser guidance ...");
}
private void setHighResolution() {
System.out.println("Set high resolution ...");
}
}
};
Testing the best
quality print requires some extra settings wrapped in the inner Tools
class. As you can see, the inner Tools
class is nested in the anonymous class. Another approach consists of moving the Tools
class inside the print()
method. So, Tools
becomes a local inner class as follows:
Printer printer = new Printer() {
@Override
public void print(String quality) {
class Tools {
private void enableLaserGuidance() {
System.out.println("Adding laser guidance ...");
}
private void setHighResolution() {
System.out.println("Set high resolution ...");
}
}
if ("best".equals(quality)) {
Tools tools = new Tools();
tools.enableLaserGuidance();
tools.setHighResolution();
}
System.out.println("Printing photo-test ...");
}
};
The problem with this approach is that the Tools
class cannot be used outside of print()
. So, this strict encapsulation will restrict us from adding a new method (next to print()
) that also needs the Tools
class.
JDK 16+
But, remember from the previous problem that, starting with JDK 16, Java inner classes can have static members and static initializers. This means that we can drop the Tools
class and rely on two static methods as follows:
Printer printer = new Printer() {
@Override
public void print(String quality) {
if ("best".equals(quality)) {
enableLaserGuidance();
setHighResolution();
}
System.out.println("Printing your photos ...");
}
private static void enableLaserGuidance() {
System.out.println("Adding laser guidance ...");
}
private static void setHighResolution() {
System.out.println("Set high resolution ...");
}
};
If you find it more convenient to pick up these helpers in a static class, then do it:
Printer printer = new Printer() {
@Override
public void print(String quality) {
if ("best".equals(quality)) {
Tools.enableLaserGuidance();
Tools.setHighResolution();
}
System.out.println("Printing photo-test ...");
}
private final static class Tools {
private static void enableLaserGuidance() {
System.out.println("Adding laser guidance ...");
}
private static void setHighResolution() {
System.out.println("Set high resolution ...");
}
}
};
You can practice these examples in the bundled code.