Section 3.10. Nested Types

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 type import pkg.LinkedStack.*;         // Import all nested types of LinkedStack 

In Java 5.0 and later, you can also use the import 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; 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(); }     } } 

Notice how the LinkedIterator class is nested 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 stack Iterator i = stack.new LinkedIterator();  // Create an Iterator for it 

The containing instance implicitly specifies the containing class; it is a syntax error to explicitly specify the containing class name:

 Iterator i = stack.new LinkedStack.LinkedIterator();  // Syntax error 

One 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 {   // 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());   } } 

The behavior of the previous program is pretty surprising. To make 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() {     // 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 } 

One common use for an anonymous class 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 list // 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! 

As you can see, 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 .



Java In A Nutshell
Java In A Nutshell, 5th Edition
ISBN: 0596007736
EAN: 2147483647
Year: 2004
Pages: 1220

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net