Chapter 5. Nested Classes and Interfaces

 

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.

Static Nested Types

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.

Static Nested Classes

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

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.1Consider 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?

Inner Classes

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.2Create 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?

Accessing Enclosing Objects

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.

Extending Inner Classes

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.

Inheritance, Scoping, and Hiding

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.

Local Inner Classes

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.

Inner Classes in Static Contexts

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.

Anonymous Inner Classes

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.

Inheriting Nested Types

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.

Nesting in Interfaces

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.

Modifiable Variables in Interfaces

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.

Implementation of Nested Types

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”


[1] This is a very common structuring idiom.

[2] It is hard to think of a reason why you would want to do this, and easy to get a headache reasoning about what it means.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.34.39