3.10. Nested Types
The classes,
interfaces, and enumerated types we have seen so far in this book
have all been defined as top-level classes. This means that they are
direct members of packages, defined independently of other types.
However, type definitions can also be nested within other type
definitions. These nested types , commonly known
as "
inner classes," are a
powerful and elegant feature of the Java language. A type can be
nested within another type in four ways:
- Static member types
A static member type is
any type defined as a static member of another
type. A static method is called a class method,
so, by analogy, we could call this type of nested type a
"class type," but this terminology
would obviously be confusing. A static member type behaves much like
an ordinary top-level type, but its name is part of the namespace,
rather than the package, of the containing type. Also, a static
member type can access the static members of the
class that contains it. Nested interfaces, enumerated types, and
annotation types are implicitly static, whether or not the
static keyword appears. Any type nested within an
interface or annotation is also implicitly static.
Static member types may be defined within top-level types or nested
to any depth within other static member types. A static member type
may not be defined within any other kind of nested type, however.- Nonstatic member classes
A
"nonstatic member
type" is simply a member type that is not declared
static. Since interfaces, enumerated types, and
annotations are always implicitly static, however, we usually use the
term "nonstatic member class"
instead. Nonstatic member classes may be defined within other classes
or enumerated types and are analogous to instance methods or fields.
An instance of a nonstatic member class is always associated with an
instance of the enclosing type, and the code of a nonstatic member
class has access to all the fields and methods (both
static and non-static) of its
enclosing type. Several features of Java syntax exist specifically to
work with the enclosing instance of a nonstatic member class.- Local classes
A
local class is a class defined within a block of Java code.
Interfaces, enumerated types, and annotation types may not be defined
locally. Like a local variable, a local class is visible only within
the block in which it is defined. Although local classes are not
member classes, they are still defined within an enclosing class, so
they share many of the features of member classes. Additionally,
however, a local class can access any final local
variables or parameters that are accessible in the scope of the block
that defines the class.- Anonymous classes
An
anonymous class is a kind of local class that has no name; it
combines the syntax for class definition with the syntax for object
instantiation. While a local class definition is a Java statement, an
anonymous class definition (and instantiation) is a Java expression,
so it can appear as part of a larger expression, such as method
invocation. Interfaces, enumerated types, and annotation types cannot
be defined anonymously.
Nested types have no universally adopted nomenclature. The term
"inner class" is commonly used.
Sometimes, however, inner class is used to refer to a nonstatic
member class, local class, or anonymous class, but not a static
member type. Although the terminology for describing nested types is
not always clear, the syntax for working with them is, and it is
usually clear from context which kind of nested type is being
discussed.Now we'll describe each of the four kinds of nested
types in greater detail. Each section describes the features of the
nested type, the restrictions on its use, and any special Java syntax
used with the type. These four sections are followed by an
implementation note that explains how nested types work under the
hood.
3.10.1. Static Member Types
A static member
type is much like a regular top-level type. For
convenience, however, it is nested within the namespace of another
type. Example 3-7 shows a helper interface defined
as a static member of a containing class. The example also shows how
this interface is used both within the class that contains it and by
external classes. Note the use of its hierarchical name in the
external class.
Example 3-7. Defining and using a static member interface
// A class that implements a stack as a linked list
public class LinkedStack {
// This static member interface defines how objects are linked
// The static keyword is optional: all nested interfaces are static
public static interface Linkable {
public Linkable getNext();
public void setNext(Linkable node);
}
// The head of the list is a Linkable object
Linkable head;
// Method bodies omitted
public void push(Linkable node) { ... }
public Object pop() { ... }
}
// This class implements the static member interface
class LinkableInteger implements LinkedStack.Linkable {
// Here's the node's data and constructor
int i;
public LinkableInteger(int i) { this.i = i; }
// Here are the data and methods required to implement the interface
LinkedStack.Linkable next;
public LinkedStack.Linkable getNext() { return next; }
public void setNext(LinkedStack.Linkable node) { next = node; }
}
3.10.1.1 Features of static member types
A static member type is defined as a
static member of a containing type. Any type
(class, interface, enumerated type, or annotation type) may be
defined as a static member of any other type. Interfaces, enumerated
types, and annotation types are implicitly static, whether or not the
static keyword appears in their definition.A static member type is like the other static members of a class:
static fields and static methods. Like a class method, a static
member type is not associated with any instance of the containing
class (i.e., there is no this object). A static
member type does, however, have access to all the
static members (including any other static member
types) of its containing type. A static member type can use any other
static member without qualifying its name with the name of the
containing type.A static member type has
access to all static members of its containing type, including
private members. The reverse is true as well: the
methods of the containing type have access to all members of a static
member type, including the private members. A
static member type even has access to all the members of any other
static member types, including the private members
of those types.Top-level types can be declared with or without the
public modifier, but they cannot use the
private and protected
modifiers. Static member types, however, are members and can use any
access control modifiers that other members of the containing type
can. These modifiers have the same meanings for static member types
as they do for other members of a type. In Example 3-7, the Linkable interface is
declared public, so it can be implemented by any
class that is interested in being stored on a
LinkedStack. Recall that all members of interfaces
(and annotation types) are implicitly public, so
static member types nested within interfaces or annotation types
cannot be protected or private.
3.10.1.2 Restrictions on static member types
A
static member type cannot have the same name as any of its enclosing
classes. In addition, static member types can be defined only within
top-level types and other static member types. This is actually part
of a larger prohibition against static members of
any sort within member, local, and anonymous classes.
3.10.1.3 Syntax for static member types
In code
outside the containing class, a static member type is named by
combining the name of the outer type with the name of the inner type
(e.g., LinkedStack.Linkable). You can use the
import directive to import a static member type:
import pkg.LinkedStack.Linkable; // Import a specific nested typeIn Java 5.0 and later, you can also use the import
import pkg.LinkedStack.*; // Import all nested types of LinkedStack
static directive to import a static member type. See Section 2.10 in Chapter 2 for details on
import and import static. Note
that importing a nested type obscures the fact that that type is
closely associated with its containing type, and it is not commonly
done.
3.10.2. Nonstatic Member Classes
A nonstatic member
class is a class that is declared as a
member of a containing class or
enumerated type without the static keyword. If a
static member type is analogous to a class field or class method, a
nonstatic member class is analogous to an instance field or instance
method. Example 3-8 shows how a member class can be
defined and used. This example extends the previous
LinkedStack example to allow enumeration of the
elements on the stack by defining an iterator( )
method that returns an implementation of the
java.util.Iterator
interface. The implementation of this interface
is defined as a member class. The example uses Java 5.0 generic type
syntax in a couple of places, but this should not prevent you from
understanding it. (Generics are covered in Chapter 4.)
Example 3-8. An iterator implemented as a member class
import java.util.Iterator;Notice how the LinkedIterator class is nested
public class LinkedStack {
// Our static member interface
public interface Linkable {
public Linkable getNext();
public void setNext(Linkable node);
}
// The head of the list
private Linkable head;
// Method bodies omitted here
public void push(Linkable node) { ... }
public Linkable pop() { ... }
// This method returns an Iterator object for this LinkedStack
public Iterator<Linkable> iterator() { return new LinkedIterator(); }
// Here is the implementation of the Iterator interface,
// defined as a nonstatic member class.
protected class LinkedIterator implements Iterator<Linkable> {
Linkable current;
// The constructor uses the private head field of the containing class
public LinkedIterator() { current = head; }
// The following 3 methods are defined by the Iterator interface
public boolean hasNext() { return current != null; }
public Linkable next() {
if (current == null) throw new java.util.NoSuchElementException();
Linkable value = current;
current = current.getNext();
return value;
}
public void remove() { throw new UnsupportedOperationException(); }
}
}
within the LinkedStack class. Since
LinkedIterator is a helper class used only within
LinkedStack, there is real elegance to having it
defined so close to where it is used by the containing class.
3.10.2.1 Features of member classes
Like
instance fields and instance methods, every instance of a nonstatic
member class is associated with an instance of the class in which it
is defined. This means that the code of a member class has access to
all the instance fields and instance methods (as well as the
static members) of the containing class, including
any that are declared private.This crucial feature is illustrated in Example 3-8.
Here is the LinkedStack.LinkedIterator()
constructor again:
public LinkedIterator() { current = head; }This single line of code sets the current field of
the inner class to the value of the head field of
the containing class. The code works as shown, even though
head is declared as a private
field in the containing class. A nonstatic member class, like any member
of a class, can be assigned one of three
visibility levels:
public, protected, or
private. If none of these visibility modifiers is
specified, the default package visibility is used. In Example 3-8, the LinkedIterator class
is declared protected, so it is inaccessible to
code (in a different package) that uses the
LinkedStack class but is accessible to any class
that subclasses LinkedStack.
3.10.2.2 Restrictions on member classes
Member classes have three important
restrictions:
- A nonstatic member class cannot have the same name as any containing
class or package. This is an important rule, one not shared by fields
and methods. - Nonstatic member classes cannot contain any static
fields, methods, or types, except for constant fields declared both
static and final.
static members are top-level constructs not
associated with any particular object while every member class is
associated with an instance of its enclosing class. Defining a
static top-level member within a member class that
is not at the top level would cause confusion, so it is not allowed.
Only classes may be defined as nonstatic
members. Interfaces, enumerated types, and annotation types are all
implicitly static, even if the static keyword is
omitted.
3.10.2.3 Syntax for member classes
The
most important feature of a member class is that it can access the
instance fields and methods in its containing object. We saw this in
the LinkedStack.LinkedIterator() constructor of
Example 3-8:
public LinkedIterator() { current = head; }In this example, head is a field of the
LinkedStack class, and we assign it to the
current field of the
LinkedIterator class. What if we want to make
these references explicit? We could try code like this:
public LinkedIterator() { this.current = this.head; }This code does not compile, however.
this.current is fine; it is an explicit reference
to the current field in the newly created
LinkedIterator object. It is the
this.head expression that causes the problem; it
refers to a field named head in the
LinkedIterator object. Since there is no such
field, the compiler generates an error. To solve this problem, Java
defines a special syntax for explicitly referring to the containing
instance of the this object. Thus, if we want to
be explicit in our constructor, we can use the following syntax:
public LinkedIterator() { this.current = LinkedStack.this.head; }The general syntax is
classname.this, where
classname is the name of a containing
class. Note that member classes can themselves contain member
classes, nested to any depth. Since no member class can have the same
name as any containing class, however, the use of the enclosing class
name prepended to this is a perfectly general way
to refer to any containing instance. This syntax is needed only when
referring to a member of a containing class that is hidden by a
member of the same name in the member class.
3.10.2.3.1 Accessing superclass members of the containing class
When a class shadows or overrides a
member of its superclass, you can use the keyword
super to refer to the hidden member. This
super syntax can be extended to work with member
classes as well. On the rare occasion when you need to refer to a
shadowed field f or an overridden method
m of a superclass of a containing class
C, use the following expressions:
C.super.f
C.super.m()
3.10.2.3.2 Specifying the containing instance
As we've seen, every
instance of a member class is associated with an instance of its
containing class. Look again at our definition of the
iterator() method in Example 3-8:
public Iterator<Linkable> iterator() { return new LinkedIterator(); }When a member class
constructor is invoked like this, the new instance of the member
class is automatically associated with the this
object. This is what you would expect to happen and exactly what you
want to occur in most cases. Occasionally, however, you may want to
specify the containing instance explicitly when instantiating a
member class. You can do this by preceding the new
operator with a reference to the containing instance. Thus, the
iterator() method shown earlier is shorthand for
the following:
public Iterator<Linkable> iterator() { return this.new LinkedIterator(); }Let's pretend we didn't define an
iterator( ) method for
LinkedStack. In this case, the code to obtain an
LinkedIterator object for a given
LinkedStack object might look like this:
LinkedStack stack = new LinkedStack(); // Create an empty stackThe containing instance implicitly specifies the containing class; it
Iterator i = stack.new LinkedIterator(); // Create an Iterator for it
is a syntax error to explicitly specify the containing class name:
Iterator i = stack.new LinkedStack.LinkedIterator(); // Syntax errorOne other special piece of Java syntax specifies an enclosing
instance for a member class explicitly. Before we consider it,
however, let me point out that you should rarely, if ever, need to
use this syntax. It is one of the pathological cases that snuck into
the language along with all the elegant features of nested types.As strange as it may seem, it is
possible for a top-level class to extend a member class. This means
that the subclass does not have a containing instance, but its
superclass does. When the subclass constructor invokes the superclass
constructor, it must specify the containing instance. It does this by
prepending the containing instance and a period to the
super keyword. If we had not declared our
LinkedIterator class to be a
protected member of
LinkedStack, we could subclass it. Although it is
not clear why we would want to do so, we could write code like the
following:
// A top-level class that extends a member class
class SpecialIterator extends LinkedStack.LinkedIterator {
// The constructor must explicitly specify a containing instance
// when invoking the superclass constructor.
public SpecialIterator(LinkedStack s) { s.super(); }
// Rest of class omitted...
}
3.10.2.4 Scope versus inheritance
We've
just noted that a top-level class can extend a member class. With the
introduction of nonstatic member classes, two separate hierarchies
must be considered for any class. The first is the
inheritance hierarchy , from superclass to
subclass, that defines the fields and methods a member class
inherits. The second is the containment
hierarchy , from containing class to contained class, that
defines a set of fields and methods that are in the scope of (and are
therefore accessible to) the member class.The two hierarchies are entirely
distinct from each other; it is important that you do not confuse
them. This should not be a problem if you refrain from creating
naming conflicts, where a field or method in a superclass has the
same name as a field or method in a containing class. If such a
naming conflict does arise, however, the inherited field or method
takes precedence over the field or method of the same name in the
containing class. This behavior is logical: when a class inherits a
field or method, that field or method effectively becomes part of
that class. Therefore, inherited fields and methods are in the scope
of the class that inherits them and take precedence over fields and
methods by the same name in enclosing scopes.A good way to prevent confusion between the class hierarchy and the
containment hierarchy is to avoid deep containment hierarchies. If a
class is nested more than two levels deep, it is probably going to
cause more confusion than it is worth. Furthermore, if a class has a
deep class hierarchy (i.e., it has many ancestors), consider defining
it as a top-level class rather than as a
nonstatic member class.
3.10.3. Local Classes
A local class is
declared locally within a block of Java code rather than as a member
of a class. Only classes may be defined locally: interfaces,
enumerated types and annotation types must be top-level or static
member types. Typically, a local class is defined within a method,
but it can also be defined within a static initializer or instance
initializer of a class. Because all blocks of Java code appear within
class definitions, all local classes are nested within containing
classes. For this reason, local classes share many of the features of
member classes. It is usually more appropriate, however, to think of
them as an entirely separate kind of nested type. A local class has
approximately the same relationship to a member class as a local
variable has to an instance variable of a class.The defining characteristic of a local
class is that it is local to a block of code. Like a local variable,
a local class is valid only within the scope defined by its enclosing
block. If a member class is used only within a single method of its
containing class, for example, there is usually no reason it cannot
be coded as a local class rather than a member class. Example 3-9 shows how we can modify the
iterator() method of the
LinkedStack class so it defines
LinkedIterator as a local class instead of a
member class. By doing this, we move the definition of the class even
closer to where it is used and hopefully improve the clarity of the
code even further. For brevity, Example 3-9 shows
only the iterator( ) method, not the entire
LinkedStack class that contains it.
Example 3-9. Defining and using a local class
// This method returns an Iterator object for this LinkedStack
public Iterator<Linkable> Iterator() {
// Here's the definition of LinkedIterator as a local class
class LinkedIterator implements Iterator<Linkable> {
Linkable current;
// The constructor uses the private head field of the containing class
public LinkedIterator() { current = head; }
// The following 3 methods are defined by the Iterator interface
public boolean hasNext() { return current != null; }
public Linkable next() {
if (current == null) throw new java.util.NoSuchElementException();
Linkable value = current;
current = current.getNext();
return value;
}
public void remove() { throw new UnsupportedOperationException(); }
}
// Create and return an instance of the class we just defined
return new LinkedIterator();
}
3.10.3.1 Features of local classes
Local classes have the following interesting features:
- Like member classes, local classes are associated with a containing
instance and can access any members, including
private members, of the containing class. - In addition to accessing fields defined by the containing class,
local classes can access any local variables, method parameters, or
exception parameters that are in the scope of the local method
definition and are declared final.
3.10.3.2 Restrictions on local classes
Local classes are subject to the following
restrictions:
- The name of a local class is defined only within the block that
defines it; it can never be used outside that block. (Note however
that instances of a local class created within the scope of the class
can continue to exist outside of that scope. This situation is
described in more detail later in this section.) - Local classes cannot be declared
public , protected,
private, or
static. These modifiers are for members of
classes; they are not allowed with local variable declarations or
local class declarations. - Like member classes, and for the same reasons, local classes cannot
contain static fields, methods, or classes. The
only exception is for constants that are declared both
static and final. - Interfaces,
enumerated types, and annotation types
cannot be defined locally. - A local class, like a member class, cannot have the same name as any
of its enclosing classes. - As noted earlier, a local class can use the local variables, method
parameters, and even exception parameters that are in its scope but
only if those variables or parameters are declared
final. This is because the lifetime of an instance
of a local class can be much longer than the execution of the method
in which the class is defined. For this reason, a local class must
have a private internal copy of all local variables it uses (these
copies are automatically generated by the compiler). The only way to
ensure that the local variable and the private copy are always the
same is to insist that the local variable is
final.
3.10.3.3 Syntax for local classes
In Java 1.0,
only
fields, methods, and classes could be declared
final. The addition of local classes in Java 1.1
required a liberalization in the use of the final
modifier. As of Java 1.1, final can be applied to
local variables, method parameters, and even the exception parameter
of a catch statement. The meaning of the
final modifier remains the same in these new uses:
once the local variable or parameter has been assigned a value, that
value cannot be changed.Instances of local classes, like instances of nonstatic member
classes, have an enclosing instance that is implicitly passed to all
constructors of the local class. Local classes can use the same
this
syntax as nonstatic member classes to refer explicitly to members of
enclosing classes. Because local classes are never visible outside
the blocks that define them, however, there is never a need to use
the new and super syntax used
by member classes to specify the enclosing instance explicitly.
3.10.3.4 Scope of a local class
In discussing nonstatic
member classes, we saw that a member class can access any members
inherited from superclasses and any members defined by its containing
classes. The same is true for local classes, but local classes can
also access final local variables and parameters.
The following code illustrates the many fields and variables that may
be accessible to a local class:
class A { protected char a = 'a'; }
class B { protected char b = 'b'; }
public class C extends A {
private char c = 'c'; // Private fields visible to local class
public static char d = 'd';
public void createLocalObject(final char e)
{
final char f = 'f';
int i = 0; // i not final; not usable by local class
class Local extends B
{
char g = 'g';
public void printVars()
{
// All of these fields and variables are accessible to this class
System.out.println(g); // (this.g) g is a field of this class
System.out.println(f); // f is a final local variable
System.out.println(e); // e is a final local parameter
System.out.println(d); // (C.this.d) d -- field of containing class
System.out.println(c); // (C.this.c) c -- field of containing class
System.out.println(b); // b is inherited by this class
System.out.println(a); // a is inherited by the containing class
}
}
Local l = new Local(); // Create an instance of the local class
l.printVars(); // and call its printVars() method.
}
}
3.10.3.5 Local variables, lexical scoping, and closures
A local variable
is defined within a block of code that defines its scope. A local
variable ceases to exist outside of its scope. Java is a
lexically scoped language, which means that its
concept of scope has to do with the way the source code is written.
Any code within the curly braces that define the boundaries of a
block can use local variables defined in that block.[10]
[10] This section covers advanced material; first-time readers may
want to skip it for now and return to it later.
Lexical scoping simply defines a segment of source code within which
a variable can be used. It is common, however, to think of a scope as
a temporal scopeto think of a local variable as existing from
the time the Java interpreter begins executing the block until the
time the interpreter exits the block. This is usually a reasonable
way to think about local variables and their scope.The introduction of local classes confuses the picture, however,
because local classes can use local variables, and instances of a
local class can have a lifetime much longer than the time it takes
the interpreter to execute the block of code. In other words, if you
create an instance of a local class, the instance does not
automatically go away when the interpreter finishes executing the
block that defines the class, as shown in the following code:
public class Weird {The behavior of the previous program is pretty surprising. To make
// A static member interface used below
public static interface IntHolder { public int getValue(); }
public static void main(String[] args) {
IntHolder[] holders = new IntHolder[10]; // An array to hold 10 objects
for(int i = 0; i < 10; i++) { // Loop to fill the array up
final int fi = i; // A final local variable
class MyIntHolder implements IntHolder {// A local class
public int getValue() { return fi; } // It uses the final variable
}
holders[i] = new MyIntHolder(); // Instantiate the local class
}
// The local class is now out of scope, so we can't use it. But we have
// 10 valid instances of that class in our array. The local variable
// fi is not in our scope here, but it is still in scope for the
// getValue() method of each of those 10 objects. So call getValue()
// for each object and print it out. This prints the digits 0 to 9.
for(int i = 0; i < 10; i++) System.out.println(holders[i].getValue());
}
}
sense of it, remember that the lexical scope of the methods of a
local class has nothing to do with when the interpreter enters and
exits the block of code that defines the local class.
Here's another way to think about it: each instance
of a local class has an automatically created private copy of each of
the final local variables it uses, so, in effect, it has its own
private copy of the scope that existed when it was created.The local class MyIntHolder is sometimes called a
closure . In general terms, a closure is an
object that saves the state of a scope and makes that scope available
later. Closures are useful in some styles of programming, and
different programming languages define and implement closures in
different ways. Java's closures are relatively weak
(and some would argue that they are not truly closures) because they
retain the state of only final variables.
3.10.4. Anonymous Classes
An anonymous
class is
a local class without a name. An anonymous class is defined and
instantiated in a single succinct expression using the
new operator. While a local class definition is a
statement in a block of Java code, an anonymous class definition is
an expression, which means that it can be included as part of a
larger expression, such as a method call. In practice, anonymous
classes are much more common than local classes. If you find yourself
defining a short local class and then instantiating it exactly once,
consider rewriting it using anonymous class syntax, which places the
definition and use of the class in exactly the same place.Consider Example 3-10, which shows the
LinkedIterator class
implemented as an anonymous class within the iterator(
) method of the LinkedStack class.
Compare it with Example 3-9, which shows the same
class implemented as a local class. The generic syntax in this
example is covered in Chapter 4.
Example 3-10. An enumeration implemented with an anonymous class
public Iterator<Linkable> iterator() {One common use for an anonymous class
// The anonymous class is defined as part of the return statement
return new Iterator<Linkable>() {
Linkable current;
// Replace constructor with an instance initializer
{ current = head; }
// The following 3 methods are defined by the Iterator interface
public boolean hasNext() { return current != null; }
public Linkable next() {
if (current == null) throw new java.util.NoSuchElementException();
Linkable value = current;
current = current.getNext();
return value;
}
public void remove() { throw new UnsupportedOperationException(); }
}; // Note the required semicolon. It terminates the return statement
}
is to provide a simple implementation of an adapter class. An
adapter class is one that defines code that is
invoked by some other object. Take, for example, the
list() method of the
java.io.File class. This method lists the files in
a directory. Before it returns the list, though, it passes the name
of each file to a FilenameFilter object you must
supply. This FilenameFilter object accepts or
rejects each file. When you implement the
FilenameFilter interface, you are defining an
adapter class for use with the File.list() method.
Since the body of such a class is typically
quite short, it is easy to define an adapter class as an anonymous
class. Here's how you can define a
FilenameFilter class to list only those files
whose names end with .java :
File f = new File("/src"); // The directory to listAs you can see,
// Now call the list() method with a single FilenameFilter argument
// Define and instantiate an anonymous implementation of FilenameFilter
// as part of the method invocation expression.
String[] filelist = f.list(new FilenameFilter() {
public boolean accept(File f, String s) { return s.endsWith(".java"); }
}); // Don't forget the parenthesis and semicolon that end the method call!
the syntax for defining an anonymous class and creating an instance
of that class uses the new keyword, followed by
the name of a class and a class body definition in curly braces. If
the name following the new keyword is the name of
a class, the anonymous class is a subclass of the named class. If the
name following new specifies an interface, as in
the two previous examples, the anonymous class implements that
interface and extends Object. The syntax does not
include any way to specify an extends clause, an
implements clause, or a name for the class.Because an anonymous class has no
name, it is not possible to define a constructor for it within the
class body. This is one of the basic restrictions on anonymous
classes. Any arguments you specify between the parentheses following
the superclass name in an anonymous class definition are implicitly
passed to the superclass constructor. Anonymous classes are commonly
used to subclass simple classes that do not take any constructor
arguments, so the parentheses in the anonymous class definition
syntax are often empty. In the previous examples, each anonymous
class implemented an interface and extended
Object. Since the Object( )
constructor takes no arguments, the parentheses were empty in those
examples.
3.10.4.1 Features of anonymous classes
Anonymous classes allow you to define a one-shot class exactly where
it is needed. Anonymous classes have all the features of local
classes but use a more concise syntax that can reduce clutter in your
code.
3.10.4.2 Restrictions on anonymous classes
Because an anonymous class is just
a type of local class, anonymous classes and local classes share the
same restrictions. An anonymous class cannot define any
static fields, methods, or classes, except for
static final constants.
Interfaces, enumerated types, and annotation types cannot be defined
anonymously. Also, like local classes, anonymous classes cannot be
public , private,
protected, or static.Since an anonymous class has no name, it is not possible to define a
constructor for an anonymous class. If your class requires a
constructor, you must use a local class instead. However, you can
often use an instance initializer as a substitute for a constructor.
The syntax for defining an anonymous class combines definition with
instantiation. Using an anonymous class instead of a local class is
not appropriate if you need to create more than a single instance of
the class each time the containing block is executed.
3.10.4.3 Syntax for anonymous classes
We've already
seen examples of the syntax for defining and instantiating an
anonymous class. We can express that syntax more formally as:
new class-name ( [ argument-list ] ) { class-body }or:
new interface-name () { class-body }Although they are not limited to use with anonymous classes, instance
initializers were introduced into the language for this purpose. As
described earlier in this chapter in Section 3.3.4, an instance initializer is
a block of initialization code contained within curly braces inside a
class definition. The contents of all instance initializers for a
class are automatically inserted into all constructors for the class,
including any automatically created default constructor. An anonymous
class cannot define a constructor, so it gets a default constructor.
By using an instance initializer, you can get around the fact that
you cannot define a constructor for an anonymous class.
3.10.4.4 When to use an anonymous class
As we've discussed, an
anonymous class behaves just like a local class and is distinguished
from a local class merely in the syntax used to define and
instantiate it. In your own code, when you have to choose between
using an anonymous class and a local class, the decision often comes
down to a matter of style. You should use whichever syntax makes your
code clearer. In general, you should consider using an anonymous
class instead of a local class if:
- The class has a very short body.
- Only one instance of the class is needed.
- The class is used right after it is defined.
- The name of the class does not make your code any easier to
understand.
3.10.4.5 Anonymous class indentation and formatting
The common indentation and
formatting conventions we are familiar with for block-structured
languages like Java and C begin to break down somewhat once we start
placing anonymous class definitions within arbitrary expressions.
Based on their experience with nested types, the engineers at Sun
recommend the following formatting rules:
- The opening curly brace should not be on a line by itself; instead,
it should follow the closing parenthesis of the
new operator. Similarly, the
new operator should, when possible, appear on the
same line as the assignment or other expression of which it is a
part. - The body of the anonymous class should be indented relative to the
beginning of the line that contains the new
keyword. - The closing curly brace of an anonymous class should not be on a line
by itself either; it should be followed by whatever tokens are
required by the rest of the expression. Often this is a semicolon or
a closing parenthesis followed by a semicolon. This extra punctuation
serves as a flag to the reader that this is not just an ordinary
block of code and makes it easier to understand anonymous classes in
a code
listing.
3.10.5. How Nested Types Work
The preceding sections explained the
features and behavior of the four kinds of nested types. Strictly
speaking, that should be all you need to know about nested types. You
may find it easier to understand nested types if you understand how
they are implemented, however.Nested types were added in Java 1.1. Despite the dramatic changes to
the Java language, the introduction of nested types did not change
the Java Virtual Machine or the Java class file format. As far as the
Java interpreter is concerned, there is no such thing as a nested
type: all classes are normal top-level classes. In order to make a
nested type behave as if it is actually defined inside another class,
the Java compiler ends up inserting hidden fields, methods, and
constructor arguments into the classes it generates. You may want to
use the javap disassembler to disassemble some
of the class files for nested types so you can see what tricks the
compiler has used to make the nested types work. (See Chapter 8 for information on
javap .)
3.10.5.1 Static member type implementation
Recall our first
LinkedStack example (Example 3-7), which defined a static member interface
named Linkable. When you compile this
LinkedStack class, the compiler actually generates
two class files. The first one is
LinkedStack.class , as expected. The second class
file, however, is called
LinkedStack$Linkable.class . The
$ in this name is automatically inserted by the
Java compiler. This second class file contains the implementation of
the static member interface.As we discussed earlier, a static member type can access all the
static members of its containing class. If a
static member type does this, the compiler automatically qualifies
the member access expression with the name of the containing class. A
static member type is even allowed to access the
private static fields of its
containing class. Since the static member type is compiled into an
ordinary top-level class, however, there is no way it can directly
access the private members of its container.
Therefore, if a static member type uses a private
member of its containing type (or vice versa), the compiler generates
synthetic non-private access methods and converts
the expressions that access the private members
into expressions that invoke these specially generated methods. These
methods are given the default package access, which is sufficient, as
the member class and its containing class are guaranteed to be in the
same package.
3.10.5.2 Nonstatic member class implementation
A nonstatic member class is implemented
much like a static member type. It is compiled into a separate
top-level class file, and the compiler performs various code
manipulations to make interclass member access work correctly.The most significant difference between a nonstatic member class and
a static member type is that each instance of a nonstatic member
class is associated with an instance of the enclosing class. The
compiler enforces this association by defining a synthetic field
named this$0 in each member class. This field is
used to hold a reference to the enclosing instance. Every nonstatic
member class constructor is given an extra parameter that initializes
this field. Every time a member class constructor is invoked, the
compiler automatically passes a reference to the enclosing class for
this extra parameter.As we've seen, a nonstatic member class, like any
member of a class, can be declared public,
protected, or private, or given
the default package visibility. Member classes are compiled to class
files just like top-level classes, but top-level classes can have
only public or package access. Therefore, as far as the Java
interpreter is concerned, member classes can have only public or
package visibility. This means that a member class declared
protected is actually treated as a public class,
and a member class declared private actually has
package visibility. This does not mean you should never declare a
member class as protected or
private. Although the Java VM cannot enforce these
access control modifiers, the modifiers are stored in the class file
and conforming Java compilers do enforce them.
3.10.5.3 Local and anonymous class implementation
A local class is able to
refer to fields and methods in its containing class for exactly the
same reason that a nonstatic member class can; it is passed a hidden
reference to the containing class in its constructor and saves that
reference away in a private synthetic field added
by the compiler. Also, like nonstatic member classes, local classes
can use private fields and methods of their
containing class because the compiler inserts any required accessor
methods.What makes local classes different from member classes is that they
have the ability to refer to local variables in the scope that
defines them. The crucial restriction on this ability, however, is
that local classes can reference only local variables and parameters
that are declared final. The reason for this
restriction becomes apparent in the implementation. A local class can
use local variables because the compiler automatically gives the
class a private instance field to hold a copy of
each local variable the class uses. The compiler also adds hidden
parameters to each local class constructor to initialize these
automatically created private fields. A local
class does not actually access local variables but merely its own
private copies of them. The only way this can work correctly is if
the local variables are declared final so that
they are guaranteed not to change. With this guarantee, the local
class can be assured that its internal copies of the variables are
always in sync with the real local variables.Since anonymous classes have no names,
you may wonder what the class files that represent them are named.
This is an implementation detail, but Sun's Java
compiler uses numbers to provide anonymous class names. If you
compile the example code shown in Example 3-10,
you'll find that it produces a class file for the
anonymous class with a name like
LinkedStack$1.class .