7.3 Non-static Member Classes

Non-static member classes are inner classes that are defined without the keyword static, as members of an enclosing class or interface. Non-static member classes are on par with other non-static members defined in a class. The following aspects about non-static member classes should be noted:

  • An instance of a non-static member class can only exist with an instance of its enclosing class. This means that an instance of a non-static member class must be created in the context of an instance of the enclosing class. This also means that a non-static member class cannot have static members. In other words, the non-static member class does not provide any services, only instances of the class do. However, final static variables are allowed, as these are constants.

  • Code in a non-static member class can directly refer to any member (including nested) of any enclosing class or interface, including private members. No explicit reference is required.

  • Since a non-static member class is a member of an enclosing class, it can have any accessibility: public, package/default, protected, or private.

A typical application of non-static member classes is implementing data structures. Code below outlines implementing a linked list, where the Node class is nested in the LinkedList class. Since the non-static member class Node is declared private, it is not accessible outside of class LinkedList. Nesting promotes encapsulation, and the close proximity allows classes to exploit each others capabilities.

class LinkedList {                                             // (1)
    private class Node {                                       // (2)
        private Object data;    // Data
        private Node next;      // Next node
        // ...
    }

    protected Node head;
    protected Node tail;
    // ...
}

Instantiating Non-static Member Classes

In Example 7.5, the class ToplevelClass at (1) defines a non-static member class at (5). Declaration of a static variable at (6) in class NonStaticMemberClass is flagged as a compile-time error, but defining a final static variable at (7) is allowed.

Example 7.5 Defining Non-static Member Classes
class ToplevelClass {                                           // (1)
    private String headlines = "Shine the inner light";         // (2)
    public NonStaticMemberClass makeInstance() {                // (3)
        return new NonStaticMemberClass();                      // (4)
    }
    public class NonStaticMemberClass {                         // (5) NSMC
    //  static int sf = 2003;                                   // (6) Not OK.
        final static int fsf = 2003;                            // (7) OK.
        private String banner;                                  // (8)
        public NonStaticMemberClass() { banner = headlines; }   // (9)
        public void print(String prefix) {
            System.out.println(prefix + banner + " in " + fsf);}// (10)
    }
}

public class Client {                                           // (11)
    public static void main(String[] args) {                    // (12)
        ToplevelClass topRef = new ToplevelClass();             // (13)
        ToplevelClass.NonStaticMemberClass innerRef1 =
                      topRef.makeInstance();                    // (14)
        innerRef1.print("innerRef1: ");                         // (15)
    //  ToplevelClass.NonStaticMemberClass innerRef2 =
    //                new ToplevelClass.NonStaticMemberClass(); // (16) Not OK.
        ToplevelClass.NonStaticMemberClass innerRef3 =
                     topRef.new NonStaticMemberClass();         // (17)
        innerRef3.print("innerRef3: ");                         // (18)
    }
}

Output from the program:

innerRef1: Shine the inner light in 2003
innerRef3: Shine the inner light in 2003

A special form of the new operator is used to instantiate a non-static member class.


<enclosing object reference>.new <non-static member class constructor call>

The <enclosing object reference> in the object creation expression evaluates to an instance of the enclosing class in which the designated non-static member class is defined. A new instance of the non-static member class is created and associated with the indicated instance of the enclosing class. Note that the expression returns a reference value that denotes a new instance of the non-static member class. It is illegal to specify the full name of the non-static member class in the constructor call, as the enclosing context is already given by the <enclosing object reference>.

The non-static method makeInstance() at (3) in the class ToplevelClass creates an instance of the NonStaticMemberClass using the new operator, as shown at (4):

return new NonStaticMemberClass();                      // (4)

This creates an instance of a non-static member class in the context of the instance of the enclosing class on which the makeInstance() method is invoked. The new operator in the statement at (4) has an implicit this reference as the <enclosing object reference>, since the non-static member class is directly defined in the context of the object denoted by the this reference:

return this.new NonStaticMemberClass();                 // (4')

The makeInstance() method is called at (14). This associates a new object of the NonStaticMemberClass with the object denoted by the reference topRef. This object of the NonStaticMemberClass is denoted by the reference innerRef1. The reference innerRef1 can then be used in the normal way to access members of this object of the non-static member class as shown at (15).

An attempt to create an instance of the non-static member class without an outer instance, using the new operator with the full name of the inner class, as shown at (16), results in a compile-time error.

The special form of the new operator is also used in the object creation expression at (17).

ToplevelClass.NonStaticMemberClass innerRef3 =
                     topRef.new NonStaticMemberClass();         // (17)

The reference topRef denotes an object of the class ToplevelClass. After the execution of the statement at (17), the ToplevelClass object has two instances of the non-static member class NonStaticInnerClass associated with it. This is depicted in Figure 7.2, where the outer object (denoted by topRef) of class ToplevelClass is shown with its two associated inner objects (denoted by references innerRef1 and innerRef3, respectively) right after the execution of the statement at (17). In other words, multiple objects of the non-static member classes can be associated with an object of an enclosing class at runtime.

Figure 7.2. Outer Object with Associated Inner Objects

graphics/07fig02.gif

Accessing Members in Enclosing Context

An implicit reference to the enclosing object is always available in every method and constructor of a non-static member class. A method can explicitly use this reference with a special form of the this construct, as explained in the next example.

From within a non-static member class, it is possible to refer to all members in the enclosing class directly. An example is shown at (9), where the field headlines from the enclosing class is accessed in the non-static member class. It is also possible to explicitly refer to members in the enclosing class, but this requires special usage of the this reference. One might be tempted to define the constructor at (9) as follows:

public NonStaticMemberClass() { this.banner = this.headlines; }

The reference this.banner is correct, because the field banner certainly belongs to the current object (denoted by this) of NonStaticMemberClass, but this.headlines cannot possibly work, as the current object (indicated by this) of NonStaticMember Class has no field headlines. The correct syntax is the following:

public NonStaticMemberClass() { this.banner = ToplevelClass.this.headlines; }

The expression


<enclosing class name>.this

evaluates to a reference that denotes the enclosing object (of class <enclosing class name>) of the current instance of a non-static member class.

Accessing Hidden Members

Fields and methods in the enclosing context can be hidden by fields and methods with the same names in the non-static member class. The special form of the this syntax can be used to access members in the enclosing context, somewhat analogous to using the keyword super in subclasses to access hidden superclass members.

Example 7.6 Special Form of this and new Constructs in Non-static Member Classes
// Filename: Client2.java
class TLClass {                                               // (1) TLC
    private String id = "TLClass object ";                    // (2)
    public TLClass(String objId) { id = id + objId; }         // (3)
    public void printId() {                                   // (4)
        System.out.println(id);
    }

    class InnerB {                                            // (5) NSMC
        private String id = "InnerB object ";                 // (6)
        public InnerB(String objId) { id = id + objId; }      // (7)
        public void printId() {                               // (8)
            System.out.print(TLClass.this.id + " : ");        // (9)
            System.out.println(id);                           // (10)
        }

        class InnerC {                                        // (11) NSMC
            private String id = "InnerC object ";             // (12)
            public InnerC(String objId) { id = id + objId; }  // (13)
            public void printId() {                           // (14)
                System.out.print(TLClass.this.id + " : ");    // (15)
                System.out.print(InnerB.this.id + " : ");     // (16)
                System.out.println(id);                       // (17)
            }
            public void printIndividualIds() {                // (18)
                TLClass.this.printId();                       // (19)
                InnerB.this.printId();                        // (20)
                printId();                                    // (21)
            }
        }
    }
}
public class OuterInstances {                                       // (22)
    public static void main(String[] args) {                        // (23)
        TLClass a = new TLClass("a");                               // (24)
        TLClass.InnerB b = a.new InnerB("b");                       // (25)
        b.printId();                                                // (26)
        TLClass.InnerB.InnerC c = b.new InnerC("c");                // (27)
        c.printId();                                                // (28)
        TLClass.InnerB.InnerC d = b.new InnerC("d");                // (29)
        d.printId();                                                // (30)
        TLClass.InnerB bb = new TLClass("aa").new InnerB("bb");     // (31)
        bb.printId();                                               // (32)
        TLClass.InnerB.InnerC cc = bb.new InnerC("cc");             // (33)
        cc.printId();                                               // (34)
        TLClass.InnerB.InnerC ccc =
            new TLClass("aaa").new InnerB("bbb").new InnerC("ccc"); // (35)
        ccc.printId();                                              // (36)
        System.out.println("------------");
        ccc.printIndividualIds();                                   // (37)
    }
}

Output from the program:

TLClass object a : InnerB object b
TLClass object a : InnerB object b : InnerC object c
TLClass object a : InnerB object b : InnerC object d
TLClass object aa : InnerB object bb
TLClass object aa : InnerB object bb : InnerC object cc
TLClass object aaa : InnerB object bbb : InnerC object ccc
------------
TLClass object aaa
TLClass object aaa : InnerB object bbb
TLClass object aaa : InnerB object bbb : InnerC object ccc

Example 7.6 illustrates the special form of the this construct employed to access members in the enclosing context, and also demonstrates the special form of the new construct employed to create instances of non-static member classes. The example shows the non-static member class InnerC at (11), which is nested in the non-static member class InnerB at (5), which in turn is nested in the top-level class TLClass at (1). All three classes have a private non-static String field named id and a non-static method named printId. The member name in the nested class hides the name in the enclosing context. These members are not overridden in the nested classes, as no inheritance is involved. In order to refer to the hidden members, the nested class can use the special this construct, as shown at (9), (15), (16), (19), and (20). Within the nested class InnerC, the three forms used in the following statements to access its field id are equivalent:

System.out.println(id);                      // (17)
System.out.println(this.id);                 // (17a)
System.out.println(InnerC.this.id);          // (17b)

The main() method at (23) uses the special syntax of the new operator to create objects of non-static member classes and associate them with enclosing objects. An instance of class InnerC (denoted by c) is created at (27) in the context of an instance of class InnerB (denoted by b), which was created at (25) in the context of an instance of class TLClassA (denoted by a), which in turn was created at (24). The reference c is used at (28) to invoke the method printId() declared at (14) in the nested class InnerC. This method prints the field id from all the objects associated with an instance of the nested class InnerC.

When the intervening references to an instance of a non-static member class are of no interest (i.e., if the reference values need not be stored in variables), then the new operator can be chained as shown at (31) and (35).

Note that the (outer) objects associated with the instances denoted by the references c, cc, and ccc are distinct, as evident from the program output. However, the instances denoted by references c and d have the same outer objects associated with them.

Inheritance Hierarchy and Enclosing Context

Inner classes can extend other classes, and vice versa. An inherited field (or method) in an inner subclass can hide a field (or method) with the same name in the enclosing context. Using the simple name to access this member will access the inherited member, not the one in the enclosing context.

Example 7.7 illustrates the situation outlined earlier. The standard form of the this reference is used to access the inherited member as shown at (4). The keyword super would be another alternative. To access the member from the enclosing context, the special form of the this reference together with the enclosing class name is used as shown at (5).

Example 7.7 Inheritance Hierarchy and Enclosing Context
class Superclass {
    protected double x = 3.0e+8;
}

class TopLevelClass {                       // (1) Top-level Class
    private double x = 3.14;

    class Inner extends Superclass {        // (2) Non-static member Class
        public void printHidden() {         // (3)

            // (4) x from superclass:
            System.out.println("this.x: " + this.x);

            // (5) x from enclosing context:
            System.out.println("TopLevelClass.this.x: "+TopLevelClass.this.x);
        }
    }
}

public class HiddenAndInheritedAccess {
    public static void main(String[] args) {
        TopLevelClass.Inner ref = new TopLevelClass().new Inner();
        ref.printHidden();
    }
}

Output from the program:

this.x: 3.0E8
TopLevelClass.this.x: 3.14

Some caution should be exercised when extending an inner class. Some of the subtleties involved are illustrated by Example 7.8.

The non-static member class InnerA, declared at (2) in class OuterA, is extended by the subclass SomeUnrelatedClass at (3). Note that SomeUnrelatedClass and class OuterA are not related in any way. An instance of subclass SomeUnrelatedClass is created at (8). An instance of class OuterA is explicitly passed as argument in the constructor call to SomeUnrelatedClass. The constructor at (4) for SomeUnrelatedClass has a special super() call in its body at (5). This call ensures that the constructor of the superclass InnerA has an outer object (denoted by the reference outerRef) to bind to. Using the standard super() call in the subclass constructor is not adequate, because it does not provide an outer instance for the superclass constructor to bind to. The non-default constructor at (4) and the outerRef.super() expression at (5) are mandatory to set up the proper relationships between the objects involved.

The outer object problem mentioned above does not arise if the subclass that extends an inner class is also declared within an outer class that extends the outer class of the superclass. This situation is illustrated at (6) and (7): classes InnerB and OuterB extend classes InnerA and OuterA, respectively. The type InnerA is inherited by class OuterB from its superclass OuterA. Thus an object of class OuterB can act as an outer object for an instance of class InnerA. The object creation expression at (9)

new OuterB().new InnerB();

creates an OuterB object and implicitly passes its reference to the default constructor of class InnerB. The default constructor of class InnerB invokes the default constructor of its superclass InnerA by calling super() and passing it the reference of the OuterB object, which the superclass constructor can readily bind to.

Example 7.8 Extending Inner Classes
class OuterA {                                   // (1)
    class InnerA { }                             // (2)
}
class SomeUnrelatedClass extends OuterA.InnerA { // (3) Extends NSMC at (2)

    // (4) Mandatory non-default constructor
    SomeUnrelatedClass(OuterA outerRef) {
        outerRef.super();                        // (5) Explicit super() call
    }
}

class OuterB extends OuterA {                    // (6) Extends class at (1)
    class InnerB extends OuterB.InnerA { }       // (7) Extends NSMC at (2)
}

public class Extending {
    public static void main(String[] args) {

        // (8) Outer instance passed explicitly in constructor call:
        new SomeUnrelatedClass(new OuterA());

        // (9) No outer instance passed explicitly in constructor call to InnerB:
        new OuterB().new InnerB();
    }
}