Contents | Prev | Next | Inner Classes Specification |
Here are some of the properties that make inner classes useful:
In addition, the programmer can define a class as a static
member of any
top-level class. Classes which are static
class members and classes which
are package members are both called top-level classes. They differ from inner
classes in that a top-level class can make direct use only of its own instance
variables. The ability to nest classes in this way allows any top-level class to
provide a package-like organization for a logically related group of secondary
top-level classes, all of which share full access to private members.
Inner classes and nested top-level classes are implemented by the compiler, and do not require any changes to the Java Virtual Machine. They do not break source or binary compatibility with existing Java programs.
All of the new nested class constructs are specified via transformations to Java 1.0 code that does not use inner classes. When a Java 1.1 compiler is producing Java virtual machine bytecodes, these bytecodes must represent the results of this (hypothetical) source-to-source transformation, so that binaries produced by different Java 1.1 compilers will be compatible. The bytecodes must also be tagged with certain attributes to indicate the presence of any nested classes to other Java 1.1 compilers. This is discussed further below.
Here is an incomplete class FixedStack
which implements a stack, and is
willing to enumerate the elements of the stack, from the top down:
The interface java.util.Enumeration
is used to communicate a series of
values to a client. Since FixedStack
does not (and should not!) directly
implement the Enumeration
interface, a separate adapter class is required to
present the series of elements, in the form of an Enumeration
. Of course, the
adapter class will need some sort of access to the stack's array of elements. If
the programmer puts the definition of the adapter class inside of FixedStack
,
the adapter's code can directly refer to the stack object's instance variables.
In Java, a class's non-static
members are able to refer to each other, and they
all take their meaning relative to the current instance this
. Thus, the instance
variable array
of FixedStack
is available to the instance method push
and
to the entire body of the inner class FixedStack.Enumerator
. Just as
instance method bodies "know" their current instance this
, the code within
any inner class like Enumerator
"knows" its enclosing instance, the instance of
the enclosing class from which variables like array
are fetched.
One of the ways in which the FixedStack
example is incomplete is that there
is a race condition among the operations of the FixedStack and its
Enumerator
. If a sequence of pushes and pops occurs between calls to
nextElement
, the value returned might not be properly related to previously
enumerated values; it might even be a "garbage value" from beyond the
current end of the stack. It is the responsibility of the programmer to defend
against such race conditions, or to document usage limitations for the class.
This point is discussed later. One defense against races looks like this:
The expression FixedStack.this
refers to the enclosing instance.
For the moment, we say nothing about how this code works, but Java's rules of
scoping and variable semantics precisely require what this code does. Even
after the method myEnumerate
returns, array
can still be used by the inner
object; it does not "go away" as in C. Instead, its value continues to be
available wherever that value is required, including the two methods of E
.
Note the final declaration. Local final
variables such as array
are a new
feature in 1.1. In fact, if a local variable or parameter in one class is referred to
by another (inner) class, it must be declared final
. Because of potential
synchronization problems, there is by design no way for two objects to share
access to a changeable local variable. The state variable count
could not be
coded as a local variable, unless perhaps it were changed a one-element array:
(Sometimes the combination of inheritance and lexical scoping can be
confusing. For example, if the class E
inherited a field named array
from
Enumeration
, the field would hide the parameter of the same name in the
enclosing scope. To prevent ambiguity in such cases, Java 1.1 allows inherited
names to hide ones defined in enclosing block or class scopes, but prohibits
them from being used without explicit qualification.)
E
adds little or no clarity to the
code. The problem is not that it is too short: A longer name would convey
little additional information to the maintainer, beyond what can be seen at a
glance in the class body. In order to make very small adapter classes as concise
as possible, Java 1.1 allows an abbreviated notation for local objects. A single
expression syntax combines the definition of an anonymous class with the
allocation of the instance:
In general, a new
expression (an instance creation expression) can end with a
class body. The effect of this is to take the class (or interface) named after the
new
token, and subclass it (or implement it) with the given body. The resulting
anonymous inner class has the same meaning as if the programmer had
defined it locally, with a name, in the current block of statements.
Anonymous constructs like these must be kept simple, to avoid deeply nested code. When properly used, they are more understandable and maintainable than the alternatives-named local classes or top-level adapter classes.
If an anonymous class contains more than a line or two of executable code, then its meaning is probably not self-evident, and so a descriptive local name should be given to either the class or (via a local variable) to the instance.
An anonymous class can have initializers but cannot have a constructor. The
argument list of the associated new
expression (often empty) is implicitly
passed to a constructor of the superclass.
As already hinted, if an anonymous class is derived from an interface I, the
actual superclass is Object
, and the class implements I rather than extending
it. (Explicit implements
clauses are illegal.) This is the only way an interface
name can legally follow the keyword new
. In such cases, the argument list
must always be null, to match the constructor of the actual superclass, Object
.
Inner Classes Specification (HTML generated by dkramer on March 15, 1997)
Copyright © 1996, 1997 Sun Microsystems, Inc.
All rights reserved
Please send any comments or corrections to john.rose@eng.sun.com