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.
By default, an inner class is non-static:
This fragment defines the class A which contains a non-static inner class B.
A non-static inner class can be instantiated only inside a non-static method of the outer class. This is because every instance of a non-static inner class must be associated with an instance of the outer class. In a sense, every instance of a non-static inner class exists ``inside'' an instance of the outer class. A single instance of the outer class may have associated with it more than one instance of the inner class.
Because an instance of a non-static inner class has an associated instance of the outer class, the methods of the inner class can access directly any of the members (fields or methods) of the outer class instance. For example, the f method defined above can access both x and y directly.
Use: An Enumeration Implemented as a Member Class
New Syntax for Member ClassesBy default, an inner class is non-static:
This fragment defines the class A which contains a non-static inner class B.
A non-static inner class can be instantiated only inside a non-static method of the outer class. This is because every instance of a non-static inner class must be associated with an instance of the outer class. In a sense, every instance of a non-static inner class exists ``inside'' an instance of the outer class. A single instance of the outer class may have associated with it more than one instance of the inner class.
Because an instance of a non-static inner class has an associated instance of the outer class, the methods of the inner class can access directly any of the members (fields or methods) of the outer class instance. For example, the f method defined above can access both x and y directly.
public class A { int y; public class B { int x; void f () {} } }The Java keyword this can be used in a non-static method to refer to the current object instance. Thus in the method f, this refers to an instance of the inner B class. Every non-static inner class is associated with an instance of A.this.
Use: An Enumeration Implemented 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; } } }
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). staticfields, 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 theLinkedStack.Enumerator() constructor of above link list example:
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() { current = 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 thethis.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 = 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.public Enumerator() { this.current = LinkedStack.this.head; }
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()
This syntax was not implemented by Java 1.1 compilers, but it works correctly as of Java 1.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 enumerate() method in :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 LinkedStackobject might look like this:
The containing instance implicitly specifies the name of the containing class; it is a syntax error to explicitly specify that containing class:LinkedStack stack = new LinkedStack(); // Create an empty stack Enumeration e = stack.new Enumerator(); // Create an Enumeration for it
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... }
Scope Versus Inheritance for Member Classes
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 namedx 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.
No comments:
Post a Comment