A member class is a class that is declared as a non-static member of a containing class. If a static member class is analogous to a class field or class method, a member class is analogous to an instance field or instance method. Example 3-9 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 enumerate() method that returns an implementation of the java.util.Enumeration interface. The implementation of this interface is defined as a member class.
public class LinkedStack { // Our static member interface; body omitted here... public static interface Linkable { ... } // The head of the list private Linkable head; // Method bodies omitted here public void push(Linkable node) { ... } public Linkable pop() { ... } // This method returns an Enumeration object for this LinkedStack public java.util.Enumeration enumerate() { return new Enumerator(); } // Here is the implementation of the Enumeration interface, // defined as a member class. protected class Enumerator implements java.util.Enumeration { Linkable current; // The constructor uses the private head field of the containing class public Enumerator() { current = head; } public boolean hasMoreElements() { return (current != null); } public Object nextElement() { if (current == null) throw new java.util.NoSuchElementException(); Object value = current; current = current.getNext(); return value; } } }
Notice how the Enumerator class is nested within the LinkedStack class. Since Enumerator is a helper class used only within LinkedStack, there is a real elegance to having it defined so close to where it is used by the containing class.
Like instance fields and instance methods, every member class is associated with an instance of the class within which it is defined (i.e., every instance of a member class is associated with an instance of the containing class). 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-9. Here is the body of the LinkedStack.Enumerator() constructor again:
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 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-9, the Enumerator class is declared protected, so it is inaccessible to code using the LinkedStack class, but accessible to any class that subclasses LinkedStack.
There are three important restrictions on member classes:
A member class cannot have the same name as any containing class or package. This is an important rule, and one not shared by fields and methods.
Member classes cannot contain any static fields, methods, or classes (with the exception of constant fields declared both static and final). static fields, methods, and classes 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 non-top-level member class simply promotes confusion and bad programming style, so you are required to define all static members within a top-level or static member class or interface.
Interfaces cannot be defined as member classes. An interface cannot be instantiated, so there is no object to associate with an instance of the enclosing class. If you declare an interface as a member of a class, the interface is implicitly static, making it a static member class.
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.Enumerator() constructor of Example 3-9:
public Enumerator() { current = head; }
In this example, head is a field of the LinkedStack class, and we assign it to the current field of the Enumerator class. The current code works, but what if we want to make these references explicit? We could try code like this:
public Enumerator() { 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 Enumerator object. It is the this.head expression that causes the problem; it refers to a field named head in the Enumerator 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 Enumerator() { 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.
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()
This syntax was not implemented by Java 1.1 compilers, but it works correctly as of Java 1.2.
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 enumerate() method in Example 3-9:
public Enumeration enumerate() { return new Enumerator(); }
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 enumerate() method shown above is shorthand for the following:
public Enumeration enumerate() { return this.new Enumerator(); }
Let's pretend we didn't define an enumerate() method for LinkedStack. In this case, the code to obtain an Enumerator object for a given LinkedStack object might look like this:
LinkedStack stack = new LinkedStack(); // Create an empty stack Enumeration e = stack.new Enumerator(); // Create an Enumeration for it
The containing instance implicitly specifies the name of the containing class; it is a syntax error to explicitly specify that containing class:
Enumeration e = stack.new LinkedStack.Enumerator(); // Syntax error
There is one other special piece of Java syntax that 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 inner classes.
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 Enumerator 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 SpecialEnumerator extends LinkedStack.Enumerator { // The constructor must explicitly specify a containing instance // when invoking the superclass constructor. public SpecialEnumerator(LinkedStack s) { s.super(); } // Rest of class omitted... }
We've just noted that a top-level class can extend a member class. With the introduction of member classes, there are two separate hierarchies that must be considered for any class. The first is the classhierarchy, from superclass to subclass, that defines the fields and methods a member class inherits. The second is the containmenthierarchy, 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.
Because this can be quite confusing, Java does not leave it to chance that you get it right. Whenever there is a naming conflict between an inherited field or method and a field or method in a containing class, Java requires that you explicitly specify which one you mean. For example, if a member class B inherits a field named x and is contained within a class A that also defines a field named x, you must use this.x to specify the inherited field and A.this.x to specify the field in the containing class. Any attempt to use the field x without an explicit specification of the desired instance causes a compilation error.
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 superclass ancestors), consider defining it as a top-level class, rather than as a member class.
Copyright © 2001 O'Reilly & Associates. All rights reserved.
This HTML Help has been published using the chm2web software. |