Every nonzero finite-dimensional inner product space has an orthonormal basis. It makes sense when you don't think about it. | ||
--Math Professor, U.C. Berkeley |
Classes and interfaces can be declared inside other classes and interfaces, either as members or within blocks of code. These nested classes and nested interfaces can take a number of different forms, each with its own properties.
The ability to define nested types serves two main purposes. First, nested classes and nested interfaces allow types to be structured and scoped into logically related groups. Second, and more important, nested classes can be used to connect logically related objects simply and effectively. This latter capability is used extensively by event frameworks, such as the one used in AWT
(see “java.awt — The Abstract Window Toolkit” on page 717) and the JavaBeans™ component architecture (see “java.beans — Components” on page 721).
A nested type is considered a part of its enclosing type and the two share a trust relationship in which each can access all members of the other. Differences between nested types depend on whether the nested type is a class or an interface, and whether the enclosing type is a class or an interface. Nested types are either static or not: The former allows simple structuring of types; the latter defines a special relationship between a nested object and an object of the enclosing class. Static nested types are more basic, so we cover them first.
A nested class or interface that is declared as a static
member of its enclosing class or interface acts just like any non-nested, or top-level, class or interface, except that its name and accessibility are defined by its enclosing type. The name of a nested type is expressed as EnclosingName
.
NestedName
. The nested type is accessible only if the enclosing type is accessible.
Static nested types serve as a structuring and scoping mechanism for logically related types. However, static nested types are members of their enclosing type and as such can access all other members of the enclosing type including private ones—through an appropriate object reference of course. This gives the nested type a special, privileged relationship with the enclosing type.
Because static nested types are members of their enclosing type, the same accessibility rules apply to them as for other members. For classes this means that a static nested class or interface can have private, package, protected, or public access, while for interfaces, nested types are implicitly public.
The static nested class is the simplest form of nested class. You declare one by preceding the class declaration with the static
modifier. When nested in an interface, a class declaration is always static and the modifier is, by convention, omitted. A static nested class acts just like any top-level class. It can extend any other class (including the class it is a member of),[1] implement any interface and itself be used for further extension by any class to which it is accessible. It can be declared final
or abstract
, just as a top-level class can, and it can have annotations applied to it.
Static nested classes serve as a mechanism for defining logically related types within a context in which that type makes sense. For example, on page 62 we showed a Permissions
class that bears information about a BankAccount
object. Because the Permissions
class is related to the contract of the BankAccount
class—it is how a BankAccount
object communicates a set of permissions—it is a good candidate to be a nested class:
public class BankAccount { private long number; // account number private long balance; // current balance (in cents) public static class Permissions { public boolean canDeposit, canWithdraw, canClose; } // ... }
The Permissions
class is defined inside the BankAccount
class, making it a member of that class. When permissionsFor
returns a Permissions
object, it can refer to the class simply as Permissions
in the same way it can refer to balance
without qualification: Permissions
is a member of the class. The full name of the class is BankAccount.Permissions
. This full name clearly indicates that the class exists as part of the BankAccount
class, not as a stand-alone type. Code outside the BankAccount
class must use the full name, for example:
BankAccount.Permissions perm = acct.permissionsFor(owner);
If BankAccount
were in a package named bank
, the full name of the class would be bank.BankAccount.Permissions
(packages are discussed in Chapter 18). In your own code, you could import the class BankAccount.Permissions
and then use the simple name Permissions
, but you would lose the important information about the subsidiary nature of the class.
Static nested classes are members of their enclosing type. Static nested classes enclosed in an interface are implicitly public; if enclosed by a class, you can declare them to be accessible in any way you like. You can, for example, declare a class that is an implementation detail to be private
. We declare Permissions
to be public
because programmers using BankAccount
need to use the class.
Since Permissions
is a member of BankAccount
, the Permissions
class can access all other members of BankAccount
, including all inherited members. For example, if Permissions
declared a method that took a BankAccount
object as an argument, that method would be able to directly access both the number
and balance
fields of that account. In this sense the nested class is seen as part of the implementation of the enclosing class and so is completely trusted.
There is no restriction on how a static nested class can be extended—it can be extended by any class to which it is accessible. Of course, the extended class does not inherit the privileged access that the nested class has to the enclosing class.
Nested enum classes are always static, although by convention the static
modifier is omitted from the enum
declaration. Enum classes are described in Chapter 6.
Nested interfaces are also always static and again, by convention the static
modifier is omitted from the interface declaration. They serve simply as a structuring mechanism for related types. When we look at non-static nested classes you will see that they are inherently concerned with implementation issues. Since interfaces do not dictate implementation they cannot be non-static.
Exercise 5.1: Consider the Attr
class and Attributed
interface from Chapter 4. Should one of these be a nested type of the other? If so, which way makes the most sense?
Non-static nested classes are called inner classes. Non-static class members are associated with instances of a class—non-static fields are instance variables and non-static methods operate on an instance. Similarly, an inner class is also (usually) associated with an instance of a class, or more specifically an instance of an inner class is associated with an instance of its enclosing class—the enclosing instance or enclosing object.
You often need to closely tie a nested class object to a particular object of the enclosing class. Consider, for example, a method for the BankAccount
class that lets you see the last action performed on the account, such as a deposit or withdrawal:
public class BankAccount { private long number; // account number private long balance; // current balance (in cents) private Action lastAct; // last action performed public class Action { private String act; private long amount; Action(String act, long amount) { this.act = act; this.amount = amount; } public String toString() { // identify our enclosing account return number + ": " + act + " " + amount; } } public void deposit(long amount) { balance += amount; lastAct = new Action("deposit", amount); } public void withdraw(long amount) { balance -= amount; lastAct = new Action("withdraw", amount); } // ... }
The class Action
records a single action on the account. It is not declared static
, and that means its objects exist relative to an object of the enclosing class.
The relationship between an Action
object and its BankAccount
object is established when the Action
object is created, as shown in the deposit
and withdraw
methods. When an inner class object is created, it must be associated with an object of its enclosing class. Usually, inner class objects are created inside instance methods of the enclosing class, as in deposit
and withdraw
. When that occurs the current object this
is associated with the inner object by default. The creation code in deposit
is the same as the more explicit
lastAct = this.new Action("deposit", amount);
Any BankAccount
object could be substituted for this
. For example, suppose we add a transfer operation that takes a specified amount from one account and places it in the current account—such an action needs to update the lastAct
field of both account objects:
public void transfer(BankAccount other, long amount) { other.withdraw(amount); deposit(amount); lastAct = this.new Action("transfer", amount); other.lastAct = other.new Action("transfer", amount); }
In this case we bind the second Action
object to the otherBankAccount
object and store it as the last action of the other account. Each BankAccount
object should only refer to Action
objects for which that BankAccount
object is the enclosing instance. It would make no sense above, for example, to store the same Action
object in both the current lastAct
field and other.lastAct
.
An inner class declaration is just like a top-level class declaration except for one restriction—inner classes cannot have static members (including static nested types), except for final static fields that are initialized to constants or expressions built up from constants. The rationale for allowing constants to be declared in an inner class is the same as that for allowing them in interfaces—it can be convenient to define constants within the type that uses them.
As with top-level classes, inner classes can extend any other class—including its enclosing class[2] —implement any interface and be extended by any other class. An inner class can be declared final
or abstract
, and can have annotations applied to it.
Exercise 5.2: Create a version of BankAccount
that records the last ten actions on the account. Add a history
method that returns a History
object that will return Action
objects one at a time via a next
method, returning null
at the end of the list. Should History
be a nested class? If so, should it be static or not?
The toString
method of Action
directly uses the number
field of its enclosing BankAccount
object. A nested class can access all members of its enclosing class—including private fields and methods—without qualification because it is part of the enclosing class's implementation. An inner class can simply name the members of its enclosing object to use them. The names in the enclosing class are all said to be in scope. The enclosing class can also access the private members of the inner class, but only by an explicit reference to an inner class object such as lastAct
. While an object of the inner class is always associated with an object of the enclosing class, the converse is not true. An object of the enclosing class need not have any inner class objects associated with it, or it could have many.
When deposit
creates an Action
object, a reference to the enclosing BankAccount
object is automatically stored in the new Action
object. Using this saved reference, the Action
object can always refer to the enclosing BankAccount
object's number
field by the simple name number
, as shown in toString
. The name of the reference to the enclosing object is this
preceded by the enclosing class name—a form known as qualified-this
. For example, toString
could reference the number
field of the enclosing BankAccount
object explicitly:
return BankAccount.this.number + ": " + act + " " + amount;
The qualified-this
reference reinforces the idea that the enclosing object and the inner object are tightly bound as part of the same implementation of the enclosing class. This is further reinforced by the qualified-super
reference, which allows access to members of the enclosing instance's superclass that have been hidden, or overridden, by the enclosing class. For example, given a class T
that extends S
, within T
we can invoke the superclass implementation of a method m
, by using super.m()
in an expression. Similarly, in an inner class of T
, we can invoke the same implementation of m
using T.super.m()
in an expression—a qualified-super
reference—and similarly for fields of S
hidden by fields in T
.
A nested class can have its own nested classes and interfaces. References to enclosing objects can be obtained for any level of nesting in the same way: the name of the class and this
. If class X
encloses class Y
which encloses class Z
, code in Z
can explicitly access fields of X
by using X.this
.
The language does not prevent you from deeply nesting classes, but good taste should. A doubly nested class such as Z
has three name scopes: itself, its immediate enclosing class Y
, and outermost class X
. Someone reading the code for Z
must understand each class thoroughly to know in which context an identifier is bound and which enclosing object was bound to which nested object. We recommend nesting only one level under most circumstances. Nesting more than two levels invites a readability disaster and should probably never be attempted.
An inner class can be extended just as any static nested class or top-level class can. The only requirement is that objects of the extended class must still be associated with objects of the original enclosing class or a subclass. Usually this is not a problem because the extended inner class is often declared within an extension of the outer class:
class Outer { class Inner { } } class ExtendedOuter extends Outer { class ExtendedInner extends Inner { } Inner ref = new ExtendedInner(); }
The ref
field is initialized when an ExtendedOuter
object is created. The creation of the ExtendedInner
instance uses the default no-arg constructor of ExtendedInner
, which in turn implicitly invokes the default no-arg constructor of Inner
by using super
. The constructor for Inner
requires an object of Outer
to bind to, which in this case is implicitly the current object of ExtendedOuter
.
If the enclosing class of the inner subclass is not a subclass of Outer
, or if the inner subclass is not itself an inner class, then an explicit reference to an object of Outer
must be supplied when the Inner
constructor is invoked via super
. For example:
class Unrelated extends Outer.Inner { Unrelated(Outer ref) { ref.super(); } }
When the construction of an Unrelated
object reaches the point where the superclass constructor is invoked, there must be an object of class Outer
to which the superclass object can be bound. Since Unrelated
is not itself an inner class of Outer
, there is no implicit enclosing object. Similarly, because Unrelated
is not a subclass of Outer
, the current object of Unrelated
is not a valid enclosing object. We must provide an explicit reference to an Outer
object for the superclass object to bind to. We chose to supply that reference using an argument to the Unrelated
constructor, which uses it as an explicit binding reference in the invocation of the superclass constructor.
Note that you cannot use the inner class creation syntax to externally provide an Outer
object, as in
Outer ref = new Outer(); Unrelated u = ref.new Unrelated(); // INVALID
because this syntax supplies an enclosing object for the Unrelated
class and Unrelated
is not an inner class.
An inner class can extend another, unrelated, inner class provided an appropriate enclosing instance is supplied to the superclass, as just described. The resulting inner class then has two enclosing instances—one for the extended class and one for the superclass. Such designs are convoluted, however, and are best avoided.
Within an inner class, all names declared within the enclosing class are said to be in scope—they can be used as if the inner class code were declared in the outer class. An inner class's own fields and methods (and nested types) can hide those of the enclosing object. There are two ways in which this can occur:
a field or method is declared in the inner class
a field or method is inherited by the inner class
In both cases any use of the simple name refers to the member of the inner class, whether declared or inherited. The enclosing object's field or method must be accessed explicitly using a qualified-this
expression.
In the second case, the use of the simple name can mislead a reader of the code. Consider the following:
class Host { int x; class Helper extends Unknown { void increment() { x++; } // Not what you think! } }
The increment
method appears to increment the x
field of the enclosing Host
instance. In fact, Unknown
also declares a field x
and that field is inherited by Helper
. The inherited x
field hides the x
field from the enclosing scope, and so it is the inherited x
field that is incremented not the field that is seen in the source code! To avoid giving the reader the wrong impression about what the code is doing, you should avoid the use of simple names in this situation. Instead, the reference to x
should be explicitly qualified as either this.x
or Host.this.x
, depending on which field it is meant to refer to.
An inner class method with the same name as an enclosing class method hides all overloaded forms of the enclosing class method, even if the inner class itself does not declare those overloaded forms. For example:
class Outer { void print() { } void print(int val) { } class Inner { void print() { } void show() { print(); Outer.this.print(); print(1); // INVALID: no Inner.print(int) } } }
Here the declaration of Inner.print
hides all forms of Outer.print
. When Inner.show
invokes print(1)
, the compiler reports that Inner
has no method print
that takes an integer argument. The show
method must explicitly qualify the method invocation with Outer.this
. There is a good reason to do this. Normally, a set of overloaded methods within a class have the same basic contract—they just operate on different parameter types. If an inner class method happens to have the same name as an enclosing class method, there is no reason to assume that it supports the same contract—consequently, the hiding of the enclosing class's overloaded forms prevents a completely unrelated method from being accidentally invoked. As usual, there are few reasons to hide fields or methods in this way, so the issue is best avoided in the first place. The details of how method invocations are resolved are discussed in “Finding the Right Method” on page 224.
You can define inner classes in code blocks, such as a method body, constructor, or initialization block. These local inner classes are not members of the class of which the code is a part but are local to that block, just as a local variable is. Such classes are completely inaccessible outside the block in which they are defined—there is simply no way to refer to them. But instances of such classes are normal objects that can be passed as arguments and returned from methods, and they exist until they are no longer referenced. Because local inner classes are inaccessible, they can't have access modifiers, nor can they be declared static
because, well, they are local inner classes. All of the other class modifiers, including annotations, can be applied, though only strictfp
and annotations have practical applications.
A local inner class can access all the variables that are in scope where the class is defined—local variables, method parameters, instance variables (assuming it is a non-static block), and static variables. The only restriction is that a local variable or method parameter can be accessed only if it is declared final
. The reason for this restriction relates mainly to multithreading issues (see Chapter 14) and ensures that all such variables have well-defined values when accessed from the inner class. Given that the method accessing the local variable or parameter could be invoked after the completion of the method in which the local class was defined—and hence the local variables and parameters no longer exist—the value of those variables must be frozen before the local class object is created. If needed, you can copy a non-final variable into a final one that is subsequently accessed by the local inner class.
Consider the standard interface Iterator
defined in the java.util
package. This interface defines a way to iterate through a group of objects. It is commonly used to provide access to the elements in a container object but can be used for any general-purpose iteration:
package java.util; public interface Iterator<E> { boolean hasNext(); E next() throws NoSuchElementException; void remove() throws UnsupportedOperationException, IllegalStateException; }
The hasNext
method returns true
if next
has more elements to return. The remove
method removes from the collection the last element returned by next
. But remove
is optional, which means an implementation is allowed to throw UnsupportedOperationException
. If remove
is invoked before next
, or is invoked multiple times after a single call to next
, then IllegalStateException
is thrown. NoSuchElementException
—also part of the java.util
package—is thrown if next
is invoked when there are no more elements. See “Iteration” on page 571 for more details.
Here is a simple method that returns an Iterator
to walk through an array of Object
instances:
public static Iterator<Object> walkThrough(final Object[] objs) { class Iter implements Iterator<Object> { private int pos = 0; public boolean hasNext() { return (pos < objs.length); } public Object next() throws NoSuchElementException { if (pos >= objs.length) throw new NoSuchElementException(); return objs[pos++]; } public void remove() { throw new UnsupportedOperationException(); } } return new Iter(); }
The Iter
class is local to the walkThrough
method; it is not a member of the enclosing class. Because Iter
is local to the method, it has access to all the final variables of the method—in particular the parameter objs
. It defines a pos
field to keep track of where it is in the objs
array. (This code assumes that Iterator
and NoSuchElementException
are imported from java.util
in the source that contains walkThrough
.)
Members of local inner classes can hide the local variables and parameters of the block they are declared in, just as they can hide instance fields and methods. The rules discussed on page 140 apply in all cases. The only difference is that once a local variable or parameter has been hidden it is impossible to refer to it.
We stated that an inner class is usually associated with an instance of the enclosing class. It is possible to declare a local inner class, or an anonymous inner class (see next section), in a static context: within a static method, a static initialization block, or as part of a static initializer. In these static contexts there is no corresponding instance of the enclosing class, and so the inner class instance has no enclosing class instance. In these circumstances, any attempt to use a qualified-this
expression to refer to an enclosing class's instance fields or methods will result in a compile-time error.
When a local inner class seems too much for your needs, you can declare anonymous classes that extend a class or implement an interface. These classes are defined at the same time they are instantiated with new
. For example, consider the walkThrough
method. The class Iter
is fairly lightweight and is not needed outside the method. The name Iter
doesn't add much value to the code—what is important is that it is an Iterator
object. The walkThrough
method could use an anonymous inner class instead:
public static Iterator<Object> walkThrough(final Object[] objs) { return new Iterator<Object>() { private int pos = 0; public boolean hasNext() { return (pos < objs.length); } public Object next() throws NoSuchElementException { if (pos >= objs.length) throw new NoSuchElementException(); return objs[pos++]; } public void remove() { throw new UnsupportedOperationException(); } }; }
Anonymous classes are defined in the new
expression itself, as part of a statement. The type specified to new
is the supertype of the anonymous class. Because Iterator
is an interface, the anonymous class in walkThrough
implicitly extends Object
and implements Iterator
. An anonymous class cannot have an explicit extends
or implements
clause, nor can it have any modifiers, including annotations.
Anonymous inner classes cannot have explicit constructors declared because they have no name to give the constructor. If an anonymous inner class is complex enough that it needs explicit constructors then it should probably be a local inner class. In practice, many anonymous inner classes need little or no initialization. In either case, an anonymous inner class can have initializers and initialization blocks that can access the values that would logically have been passed as a constructor argument. The only construction problem that remains is the need to invoke an explicit superclass constructor. To solve this problem the new expression is written as if a superclass instance were being constructed, and that constructor is invoked as the superclass constructor. For example, the following anonymous subclass of Attr
(see page 76) invokes the single-argument Attr
constructor and overrides setValue
to print out the new value each time it is changed:
Attr name = new Attr("Name") { public Object setValue(Object nv) { System.out.println("Name set to " + nv); return super.setValue(nv); } };
In the Iterator
example we invoked the no-arg superclass constructor for Object
—the only constructor that can ever be used when an anonymous inner class has an interface type.
Anonymous classes are simple and direct but can easily become very hard to read. The further they nest, the harder they are to understand. The nesting of the anonymous class code that will execute in the future inside the method code that is executing now adds to the potential for confusion. You should probably avoid anonymous classes that are longer than about six lines, and you should use them in only the simplest of expressions. We stretch this rule in the walkThrough
example because the sole purpose of the method is to return that object, but when a method does more, anonymous classes must be kept quite small or the code becomes illegible. When anonymous classes are used properly, they are a good tool for keeping simple classes simple. When misused, they create impenetrable inscrutability.
Nested types, whether static classes, interfaces, or inner classes, are inherited the same way that fields are inherited. A declaration of a nested type with the same name as that of an inherited nested type hides the definition of the inherited type. The actual type referred to is determined by the type of the reference used. Within a given class, the actual nested type referred to is the one defined in the current class or inherited in the current class.
Consider a framework that models devices that can be connected by different ports. The class Device
is an abstract class that captures some common behavior of all devices. A port also has some common generic behavior so it is also modeled as an abstract class and, because ports exist only within devices, the Port
class is made an inner class of Device
. A concrete device class defines the state for the device and the concrete inner port classes for that device. During construction of a concrete device class, references to the concrete ports of that class are initialized:
abstract class Device { abstract class Port { // ... } // ... } class Printer extends Device { class SerialPort extends Port { // ... } Port serial = new SerialPort(); }
A concrete device can itself be extended, as can the concrete inner port classes, to specialize their behavior.
class HighSpeedPrinter extends Printer { class SerialPort extends Printer.SerialPort { // ... } }
The intent is that the class HighSpeedPrinter.SerialPort
overrides the class Printer.SerialPort
so that serial
is set to refer to the correct type of object. But the class Printer
is not affected by the new subclass of SerialPort
defined inside HighSpeedPrinter
, even though the new subclass seems to have the same name.
One solution to this design problem is to abstract construction of the inner class objects into a factory method which can then be overridden in a subclass to construct the right kind of inner class. For example:
class Printer extends Device { class SerialPort extends Port { // ... } Port serial = createSerialPort(); protected Port createSerialPort() { return new SerialPort(); } }
The HighSpeedPrinter
class now defines its specialized inner class and overrides the factory method to construct an instance of that class. Now that we realize that nested type definitions hide and don't override, we aren't tempted to use the same name for the inner class, for we know that hiding is usually a bad thing.
class HighSpeedPrinter extends Printer { class EnhancedSerialPort extends SerialPort { // ... } protected Port createSerialPort() { return new EnhancedSerialPort(); } }
Now when a HighSpeedPrinter
is constructed and the initializers in Printer
execute, we invoke the overriding createSerialPort
method that returns an instance of EnhancedSerialPort
.
This is one example of a situation in which a method of the subclass is invoked before the subclass object has been fully constructed and so care must be taken to ensure that things work correctly. For example, if EnhancedSerialPort
initializes a field using a field from the enclosing HighSpeedPrinter
instance, then at the time the EnhancedSerialPort
object is constructed, the fields of the enclosing object will have the default “zero” values for their type.
An alternative design would have the constructor for HighSpeedPrinter
simply reassign serial
to refer to an EnhancedSerialPort
object. However, that approach causes the unnecessary construction of the original SerialPort
object, and that construction may be non-trivial and undesirable—in this example it may involve configuring hardware.
You declare nested classes and interfaces in an interface for the same reason that you declare nested classes and interfaces in a class: nested classes and interfaces allow you to associate types that are strongly related to an interface inside that interface. For example, a class that was used only to return multiple values from an interface's method could be represented as a nested class in that interface:
interface Changeable { class Record { public Object changer; public String changeDesc; } Record getLastChange(); // ... }
The method getLastChange
returns a Changeable.Record
object that contains the object that made the change and a string describing the change. This class has meaning relative only to the Changeable
interface, so making a top-level class not only is unnecessary but would also separate it from the context of its use. As a nested class it is tightly bound to its origin and context, but it is a normal class in every other regard.
Another use for a nested class within an interface is to define a (partial or complete) default implementation for that interface. A class that implements the interface could then choose to extend the default implementation class or to forward method invocations to an instance of that class.
Any class or interface nested inside an interface is public and static.
We mentioned on page 121 that if you need shared, modifiable data in an interface, then a nested class is a simple way of achieving this. Declare a nested class whose fields hold the shared data, and whose methods provide access to that data, then maintain a reference to an instance of that class. For example:
interface SharedData { class Data { private int x = 0; public int getX() { return x; } public void setX(int newX) { x = newX; } } Data data = new Data(); }
Now all implementors and users of SharedData
can share common state via the data
reference.
How the compiler and runtime system deal with nested types would ideally be transparent to the programmer. Unfortunately, this is not quite the case. Nested types were added as a language extension, and that extension had to maintain compatibility with older Java virtual machines. Consequently, nested types are implemented in terms of a source code transformation that the compiler applies.
As a programmer, the only thing you should need to know about this process is the naming conventions that are used. Consider a static, or non-local, nested type defined as Outer.Inner
. This is the source name for the class. At the virtual machine level the class is renamed as Outer$Inner
—in essence, dots in a nested name are converted to dollar signs. For local inner classes, the transformation is less specific because these classes are inaccessible, hence their name is less important—such classes are named Outer$NInner
, where Inner
is the name of the local class and N
is a sequence of one or more digits. For anonymous inner classes the names have the form Outer$N
, where N
is a sequence of one or more digits.
These issues have to be dealt with in two main cases. First, when bundling the class files for your applications you have to recognize all the strange files with $
in their name. Second, if you use the reflection mechanism discussed in Chapter 16 to create nested class instances, you'll need to know the transformed name. But in most programming the transformed name is something you can blissfully ignore.
“A power so great, it can only be used for Good or Evil!” | ||
--Firesign Theatre, “The Giant Rat of Summatra” |
3.137.217.17