Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 2 of 2
| Note |
|---|
If you compile JobIterator3 and examine the resulting class files, you will discover an Employee$JobIterator.class filename. That filename corresponds to the class file containing byte codes for the nested JobIterator class within Employee. Notice the dollar sign character. Java's compiler generates names for nested classes by prefixing their enclosing class
names and dollar sign characters to nested class names. Why use a dollar sign? It is a legitimate character that underlying
platforms allow for filenames. In contrast, period characters (.) separate the actual name from the file's extension, and are not always valid for use in platform-specific filenames.
|
Java divides nested classes into two main categories: nested top-level classes and inner classes. Furthermore, Java divides inner classes into the instance inner class, local inner class, and anonymous inner class categories. To understand nested classes, you need to understand each category. We begin exploring those categories by focusing on nested top-level classes.
When you declare a class outside any other class, Java regards that class as a top-level class. If you declare a class within a top-level class and prefix keyword static to the nested class declaration, you end up with a nested top-level class. The following code fragment demonstrates a top-level class and a nested top-level class:
class TopLevelClass
{
static class NestedTopLevelClass
{
}
}
Just as a static field and a static method (also known as a class field and a class method) are independent of any objects that you create from their enclosing classes, a nested top-level class is also independent from such objects. Consider the following code fragment:
class TopLevelClass
{
static int staticField;
int instanceField;
static class NestedTopLevelClass
{
static
{
System.out.println ("Can access staticField " + staticField);
// System.out.println ("Cannot access instanceField " + instanceField);
}
{
System.out.println ("Can access staticField " + staticField);
// System.out.println ("Cannot access instanceField " + instanceField);
}
}
}
Within the class block and object block initializers of the preceding code fragment's NestedTopLevelClass class, you can access TopLevelClass's staticField variable. However, you cannot access instanceField from within either initializer. Because NestedTopLevelClass cannot access TopLevelClass's instanceField variable, NestedTopLevelClass is independent of any TopLevelClass objects.
| Caution |
|---|
| A nested top-level class cannot access any enclosing class's instance members (fields and methods). |
Although NestedTopLevelClass cannot access TopLevelClass's instance fields, keyword static does not prevent NestedTopLevelClass from declaring its own instance fields or creating NestedTopLevelClass objects. For proof, check out Listing 4:
Listing 4. NestedTopLevelClassDemo.java
// NestedTopLevelClassDemo.java
class TopLevelClass
{
static class NestedTopLevelClass
{
int myInstanceField;
NestedTopLevelClass (int i)
{
myInstanceField = i;
}
}
}
class NestedTopLevelClassDemo
{
public static void main (String [] args)
{
TopLevelClass.NestedTopLevelClass ntlc;
ntlc = new TopLevelClass.NestedTopLevelClass (5);
System.out.println (ntlc.myInstanceField);
}
}
When run, NestedTopLevelClassDemo produces the following output:
5
NestedTopLevelClassDemo's main() method creates a NestedTopLevelClass variable -- ntlc. The syntax for declaring that variable is identical to Listing 3's Employee.JobIterator eji = e.getJobIterator (); syntax. Generally, when you need a nested class type variable, prefix the nested class type's name with the names of all
enclosing classes and period separators. The same idea holds when identifying the nested class type after keyword new: you must supply the names of all enclosing classes and period separators prior to the nested class name.
At this point, you might wonder whether you can declare a nested top-level class within a nested top-level class. Also, what happens if two different enclosing classes declare the same field variable name with different types and/or initial values? For an answer to both questions, check out Listing 5:
Listing 5. NestingAndShadowingDemo.java
// NestingAndShadowingDemo.java
class TopLevelClass
{
private static int a = 1;
private static int b = 3;
static class NestedTopLevelClass
{
private static int a = 2;
static class NestedNestedTopLevelClass
{
void printFields ()
{
System.out.println ("a = " + a);
System.out.println ("b = " + b);
}
}
}
}
class NestingAndShadowingDemo
{
public static void main (String [] args)
{
TopLevelClass.NestedTopLevelClass.NestedNestedTopLevelClass nntlc;
nntlc = new TopLevelClass.NestedTopLevelClass.
NestedNestedTopLevelClass ();
nntlc.printFields ();
}
}
When run, NestingAndShadowingDemo produces the following output:
a = 2 b = 3
The fact that NestingAndShadowingDemo compiles and runs proves that you can nest nested top-level classes within nested top-level classes. Also, the output shows
that NestedTopLevelClass's a field shadows TopLevelClass's a field. As a result, the contents of NestedTopLevelClass's a field (2) prints.
Nested top-level classes prove inadequate for accessing an enclosing class's instance fields or calling the class's instance
methods. To accommodate instance member access, Java supports inner classes. Inner classes resemble nested top-level classes, except that you never declare an inner class with the static keyword. We now examine inner classes, beginning with the instance inner class category.
| Tip |
|---|
You can declare a nested top-level class with the private, protected, or public keywords to determine that class's level of accessibility to code outside the enclosing class.
|
Suppose you declare a class within another class and omit the static keyword from that nested class's declaration. Instead of ending up with a nested top-level class, you end up with an instance inner class. Unlike nested top-level classes, which can access only enclosing classes' static members, instance inner classes can access
both static and instance members. For an example of an instance inner class, refer to Listing 3. Within class Employee, you see instance inner class JobIterator. Look carefully at both classes and you see that JobIterator can access Employee's private jobs instance field.
| Tip |
|---|
You can declare an instance inner class with the private, protected, or public keywords to determine that class's level of accessibility to code outside the enclosing class.
|
In addition to allowing class nesting within classes, Java also allows you to nest classes even closer to where they are needed
by placing a class in any block, a region of code that appears between open/close brace characters ({ }). That placement implies that the class can appear inside a method declaration and even between a pair of brace characters
that follow an if statement. Although the class still nests within some enclosing class, the nested class is now situated much closer to the
code that needs to work with that class. Such a class is known as a local inner class.
Local inner classes have one advantage over instance inner classes. In addition to being able to access enclosing classes' instance and class fields (and call the instance and class methods), local inner classes can access local variables and/or a method's parameters. Examine Listing 6 to see a local inner class:
Listing 6. LocalInnerClassDemo.java
// LocalInnerClassDemo.java
import java.util.*;
class ComputerLanguage
{
private String name;
ComputerLanguage (String name)
{
this.name = name;
}
public String toString ()
{
return name;
}
}
class LocalInnerClassDemo
{
public static void main (String [] args)
{
ComputerLanguage [] cl =
{
new ComputerLanguage ("Ada"),
new ComputerLanguage ("Algol"),
new ComputerLanguage ("APL"),
new ComputerLanguage ("Assembly - IBM 360"),
new ComputerLanguage ("Assembly - Intel"),
new ComputerLanguage ("Assembly - Mostek"),
new ComputerLanguage ("Assembly - Motorola"),
new ComputerLanguage ("Assembly - VAX"),
new ComputerLanguage ("Assembly - Zilog"),
new ComputerLanguage ("BASIC"),
new ComputerLanguage ("C"),
new ComputerLanguage ("C++"),
new ComputerLanguage ("Cobol"),
new ComputerLanguage ("Forth"),
new ComputerLanguage ("Fortran"),
new ComputerLanguage ("Java"),
new ComputerLanguage ("LISP"),
new ComputerLanguage ("Logo"),
new ComputerLanguage ("Modula 2"),
new ComputerLanguage ("Pascal"),
new ComputerLanguage ("Perl"),
new ComputerLanguage ("Prolog"),
new ComputerLanguage ("Snobol")
};
Enumeration e = enumerator ((Object []) cl);
while (e.hasMoreElements ())
System.out.println (e.nextElement ());
}
static Enumeration enumerator (final Object [] array)
{
class LocalInnerClass implements Enumeration
{
private int index = 0;
public boolean hasMoreElements ()
{
return index < array.length;
}
public Object nextElement ()
{
return array [index++].toString ();
}
}
return new LocalInnerClass ();
}
}
When run, LocalInnerClassDemo produces the following output:
Ada Algol APL Assembly - IBM 360 Assembly - Intel Assembly - Mostek Assembly - Motorola Assembly - VAX Assembly - Zilog BASIC C C++ Cobol Forth Fortran Java LISP Logo Modula 2 Pascal Perl Prolog Snobol
LocalInnerClassDemo demonstrates the declaration of local inner class LocalInnerClass within LocalInnerClassDemo's enumerator() class method. Notice that enumerator() returns a reference to an object whose class implements the Enumeration interface (located in the java.util package). Because LocalInnerClass also implements Enumeration, enumerator() can legally return a reference to a LocalInnerClass object.
A careful examination of enumerator()'s parameter list reveals a single array parameter of type Object [], and something else -- keyword final. What is the purpose of keyword final? The "Inner Classes Specification" document offers a somewhat cryptic answer:
Because of potential synchronization problems, there is by design no way for two objects to share access to a changeable local variable.The quote above implies that a nonfinal (that is, changeable) local variable (or parameter), which code within two objects -- a local inner class object and the object whose instance method declares a local inner class -- accesses, can possibly cause synchronization problems.
LocalInnerClassDemo supplies a LocalInnerClass object and the enumerator() class method, which both share the nonfinal array parameter. This is a different scenario than the quote describes. What kinds of synchronization problems arise? One possibility:
Inconsistencies arise when multiple threads attempt simultaneous access to a shared variable. (I discuss this possibility
in a future article that explores synchronization.) Another possibility, to which I believe the quote refers: A local inner
class modifies a nonfinal local variable, but that modification does not appear later in the method, as the following (illegal)
code fragment clarifies:
void someMethod (int x)
{
// Assume x contains 20
class Fred
{
Fred ()
{
x = 30; // Compiler reports error
}
}
Fred f = new Fred ();
System.out.println (x); // x still contains 20
return x;
}
someMethod() instance method with a single parameter x, which initially contains 20. Local inner class Fred's constructor assigns 30 to x (which the compiler does not allow, but let's assume otherwise). However, System.out.println (x); reveals x to contain 20 -- not 30. Talk about confusion! To allow Fred access to x, the compiler copies x within Fred. Fred's constructor modifies the copy -- not the original x. A new and unwary Java developer probably would not know that two copies of x exist and expect x to contain 30 after Fred f = new Fred (); executes. Discovering this incorrect assumption leads to confusion. To avoid this confusion, Sun forces developers to prefix
the int x parameter declaration with keyword final: void someMethod (final int x). As a result, Fred()'s x = 30; statement is illegal.| Note |
|---|
| If the local variable or parameter has primitive type, as opposed to reference type, the compiler simply substitutes a literal for that variable. |
Can LocalInnerClassDemo access array after enumerator() returns? After all, doesn't a parameter (final or otherwise) disappear once a method exits? Yes, the parameter disappears;
however, if you examine the byte codes in the appropriate class files, you will discover that the compiler performs some sleight-of-hand
code generation. First, the compiler creates a LocalInnerClass(Object val$array) constructor in LocalInnerClass. Second, the compiler creates a hidden Object val$array; field within LocalInnerClass. That field is known as a synthetic field because the compiler creates it. Finally, the compiler changes return new LocalInnerClass ();, within enumerator(), to return new LocalInnerClass (array);. Because LocalInnerClass has a copy of the reference to the same Object [] array, as passed to enumerator(), LocalInnerClass's methods can refer to that array long after enumerator()'s array parameter disappears.
| Tip |
|---|
| Learn more about the compiler's manipulation of local inner classes into top-level classes by studying the Inner Classes Specification document and by using the javap program to disassemble class files. |
When a class is short, you'll want to declare a local inner class without a name because that name won't contribute any meaning to the class, Also, by not choosing a name, you reduce the chance of name conflicts when several local inner classes appear within the same enclosing class. A local inner class that lacks a name is known as an anonymous inner class.
Because an anonymous inner class lacks a name, you declare that class where you create an object from that class, as Listing 7 demonstrates:
Listing 7. AnonymousInnerClassDemo1.java
// AnonymousInnerClassDemo1.java
abstract class Farmer
{
protected String name;
Farmer (String name)
{
this.name = name;
}
abstract void occupation ();
}
class BeefFarmer extends Farmer
{
BeefFarmer (String name)
{
super (name);
}
void occupation ()
{
System.out.println ("Farmer " + name + " raises beef cattle");
}
}
class AnonymousInnerClassDemo1
{
public static void main (String [] args)
{
BeefFarmer bf = new BeefFarmer ("John Doe");
bf.occupation ();
new Farmer ("Jane Doe")
{
void occupation ()
{
System.out.println ("Farmer " + name + " milks cows");
}
}.occupation ();
}
}
When run, AnonymousInnerClassDemo1 produces the following output:
Farmer John Doe raises beef cattle Farmer Jane Doe milks cows
AnonymousInnerClassDemo1 declares an abstract Farmer class that encapsulates a farmer's name and occupation -- via an abstract occupation() method. A BeefFarmer class extends Farmer and overrides occupation() to identify what a beef farmer does. That class appears in AnonymousInnerClassDemo1's main() method, which creates a BeefFarmer object and calls its occupation() method to print out the beef farmer's occupation. No surprises here! However, as you continue to examine main(), you encounter something strange: new Farmer ("Jane Doe") { ... }. It appears that we are trying to create an object from the abstract Farmer class, which is impossible. But what does that open brace character following ("Jane Doe") mean? Surely that is not legal Java code? In actual fact, the code is legal and reads like this: "Have the JVM create an
object from a Farmer anonymous subclass." That subclass overrides Farmer's occupation() method and calls the Farmer (String name) constructor to initialize Farmer's protected name field. After creating that object, call its occupation() method and then discard the object's reference so the object becomes eligible for garbage collection.
AnonymousInnerClassDemo1's use of a superclass constructor to initialize the resulting object's superclass layer begs the following question: can
I declare my own constructors in an anonymous class? The answer is no. Because a constructor requires a class name and because
an anonymous class has no name, how could the compiler choose a name?
| Caution |
|---|
| Attempts to declare constructors in an anonymous inner class fail because constructors must have the names of the classes in which they appear and anonymous inner classes have no names. |
Instead of constructors, you can use an object block initializer to perform custom initialization when creating an object
from an anonymous inner class. For example, suppose you want to customize the Farmer Jane Doe milks cows message in AnonymousInnerClassDemo1's anonymous subclass of Farmer. You want to pass the number of cows to be milked on the command line and have that value appear in the message. Because
an object block initializer executes during object creation, you simply perform the appropriate command line argument initialization
in an object block initializer, as Listing 8 demonstrates:
Listing 8. AnonymousInnerClassDemo2.java
// AnonymousInnerClassDemo2.java
abstract class Farmer
{
protected String name;
Farmer (String name)
{
this.name = name;
}
abstract void occupation ();
}
class BeefFarmer extends Farmer
{
BeefFarmer (String name)
{
super (name);
}
void occupation ()
{
System.out.println ("Farmer " + name + " raises beef cattle");
}
}
class AnonymousInnerClassDemo2
{
public static void main (final String [] args)
{
BeefFarmer bf = new BeefFarmer ("John Doe");
bf.occupation ();
new Farmer ("Jane Doe")
{
private String count;
{
if (args.length == 1)
count = args [0];
}
void occupation ()
{
if (count == null)
System.out.println ("Farmer " + name + " milks cows");
else
System.out.println ("Farmer " + name + " milks " +
count + " cows");
}
}.occupation ();
}
}
Assuming you type java AnonymousInnerClassDemo2 10 at the command line, you receive the following output:
Farmer John Doe raises beef cattle Farmer Jane Doe milks 10 cows
AnonymousInnerClassDemo2 still initializes the resulting object's Farmer layer, by calling constructor Farmer (String name) with Jane Doe as the object value that name references. However, the resulting object's anonymous layer also has a chance to initialize, through the object block initializer.
| Note |
|---|
Even though an anonymous inner class has no name, the compiler still needs to generate a name for a class file. It turns out
that the compiler chooses integer numbers, which append to enclosing class names and dollar sign characters, for anonymous
inner class names. For example, in AnonymousInnerClassDemo2, the compiler generates AnonymousInnerClassDemo2.class as the class file name for the anonymous inner class.
|
Before you leave this section, consider the following practical application of anonymous inner classes: Developers often use anonymous inner classes to simplify event-handling -- that is, notifications of significant activities, such as moving the mouse or pressing a key -- in programs that generate and display graphical user interfaces (GUIs). Using an anonymous inner class for event handlers proves convenient because they often do not require class names. Listing 9 presents an example where an anonymous inner class facilitates the handling of the window-closing event:
Listing 9. AnonymousInnerClassDemo3.java
// AnonymousInnerClassDemo3.java
import java.awt.*;
import java.awt.event.*;
class AnonymousInnerClassDemo3
{
public static void main (String [] args)
{
// Create a rectangular frame window with a title bar at the top.
Frame f = new Frame ("Anonymous Inner Class Demo #3");
// Add a window listener that will generate a window closing event
// in response to user attempts to click the little X button (on
// Windows platforms) to the right of the title bar. When the user
// clicks that button, the window closing event results in a call
// to a method named windowClosing(). By calling System.exit (0);
// from within that method, the application exits.
f.addWindowListener (new WindowAdapter ()
{
public void windowClosing (WindowEvent e)
{
System.exit (0);
}
});
// Establish the frame window's size as 300 horizontal pixels by
// 100 vertical pixels.
f.setSize (300, 100);
// Display the frame window and get the underlying event handling
// system running.
f.setVisible (true);
}
}
When run, AnonymousInnerClassDemo3 displays a rectangular window, known as a frame window, because it is the resulting GUI's main window. Once the frame window
appears, however, the user should be able to remove that window and stop AnonymousInnerClassDemo3's execution. That termination typically occurs in response to the user clicking a little button, labeled X, to the top right of the window's title bar -- at least on Windows platforms.
When the user clicks X, the underlying Java windowing toolkit creates an event object and calls a special method -- windowClosing (WindowEvent e), where e contains a reference to that event object -- in an object known as the frame window's window listener. That listener object
registers with the underlying toolkit (so the toolkit knows the method location) by calling Frame's addWindowListener (WindowListener wl) method.
The WindowListener interface declares several methods, one method for each potential window event. To spare the developer from having to supply
code for all those methods, Java's windowing toolkit designers built a WindowAdapter class that implements all WindowListener methods. Because those implemented methods are empty stubs, the developer declares an anonymous inner class that extends
WindowAdapter and overrides one or more of those methods with specific code. As you can see, I have chosen to override windowClosing (WindowEvent e) and have that method call System.exit (0); to terminate the program (which also closes the window), in response to a call made by the windowing toolkit to windowClosing (WindowEvent e).
| Note |
|---|
| You will often work with anonymous inner classes when developing event handlers for your GUIs. In future articles, I will provide many examples of event handlers built from anonymous inner classes. |
This article has shown class nesting to be a useful addition to the Java language. Not only does class nesting help clarify source code -- because you can declare classes closer to the objects they manipulate -- it also helps reduce the number of conflicts between the names of classes declared at the same level within a source file.
There are four categories of nested classes: nested top-level classes, instance inner classes, local inner classes, and anonymous
inner classes. Nested top-level classes can only access the class fields and call the class methods in any enclosing class.
In contrast, instance inner classes can access an enclosing class's class and instance fields, and call an enclosing class's
class or instance methods. Because nested top-level classes and instance inner classes can only appear within the confines
of another class, Java supplies local inner classes that can appear within any block -- including a method block or the block
following an if statement. Furthermore, because some local inner classes are so short they do not warrant their own class names, Java supplies
anonymous inner classes. Local and anonymous inner classes can access the local variables and parameters accessible within
their enclosing blocks, provided that you mark those local variables and parameters final.
I encourage you to email me with any questions you might have involving either this or any previous article's material. (Please, keep such questions relevant to material discussed in this column's articles.) Your questions and my answers will appear in the relevant study guides.
In next month's article, you will learn about exceptions and how to handle them.