6.3 Chaining Constructors Using 'this()' and 'super()'

Constructors are discussed in Section 4.4 on page 117. Other uses of the keywords this and super can be found in Section 6.2 on page 233.

this() Constructor Call

Constructors cannot be inherited or overridden. They can be overloaded, but only in the same class. Since a constructor always has the same name as the class, each parameter list must be different when defining more than one constructor for a class. In Example 6.6, the class Light has three overloaded constructors. In the non-default constructor at (3), the this reference is used to access the fields shadowed by the parameters. In the main() method at (4), the appropriate constructor is invoked depending on the arguments in the constructor call, as illustrated by the program output.

Example 6.6 Constructor Overloading
class Light {

    // Fields
    private int     noOfWatts;      // wattage
    private boolean indicator;      // on or off
    private String  location;       // placement
    // Constructors
    Light() {                                      // (1) Explicit default constructor
        noOfWatts = 0;
        indicator = false;
        location  = "X";
        System.out.println("Returning from default constructor no. 1.");
    }
    Light(int watts, boolean onOffState) {                      // (2) Non-default
        noOfWatts = watts;
        indicator = onOffState;
        location  = "X";
        System.out.println("Returning from non-default constructor no. 2.");
    }
    Light(int noOfWatts, boolean indicator, String location) {  // (3) Non-default
        this.noOfWatts = noOfWatts;
        this.indicator = indicator;
        this.location  = location;
        System.out.println("Returning from non-default constructor no. 3.");
    }
}

public class DemoConstructorCall {
    public static void main(String[] args) {                    // (4)
        System.out.println("Creating Light object no. 1.");
        Light light1 = new Light();
        System.out.println("Creating Light object no. 2.");
        Light light2 = new Light(250, true);
        System.out.println("Creating Light object no. 3.");
        Light light3 = new Light(250, true, "attic");
    }
}

Output from the program:

Creating Light object no. 1.
Returning from default constructor no. 1.
Creating Light object no. 2.
Returning from non-default constructor no. 2.
Creating Light object no. 3.
Returning from non-default constructor no. 3.

Example 6.7 illustrates the use of the this() construct, which is used to implement local chaining of constructors in the class when an instance of the class is created. The first two constructors at (1) and (2) from Example 6.6 have been rewritten using the this() construct. The this() construct can be regarded as being locally overloaded, since its parameters (and hence its signature) can vary, as shown in the body of the constructors at (1) and (2). The this() call invokes the constructor with the corresponding parameter list. In the main() method at (4), the appropriate constructor is invoked depending on the arguments in the constructor call when each of the three Light objects are created. Calling the default constructor to create a Light object results in the second and third constructors being executed as well.

This is confirmed by the output from the program. In this case, the output shows that the third constructor completed first, followed by the second, and finally the default constructor that was called first. Bearing in mind the definition of the constructors, the constructors are invoked in the reverse order; that is, invocation of the default constructor immediately leads to invocation of the second constructor by the call this(0, false), and its invocation leads to the third constructor being called immediately by the call this(watt, ind, "X"), with the completion of the execution in the reverse order of their invocation. Similarly, calling the second constructor to create an instance of the Light class results in the third constructor being executed as well.

Java requires that any this() call must occur as the first statement in a constructor. The this() call can be followed by any other relevant code. This restriction is due to Java's handling of constructor invocation in the superclass when an object of the subclass is created. This mechanism is explained in the next subsection.

Example 6.7 this() Constructor Call
class Light {
    // Fields
    private int     noOfWatts;
    private boolean indicator;
    private String  location;

    // Constructors
    Light() {                              // (1) Explicit default constructor
        this(0, false);
        System.out.println("Returning from default constructor no. 1.");
    }
    Light(int watt, boolean ind) {         // (2) Non-default
        this(watt, ind, "X");
        System.out.println("Returning from non-default constructor no. 2.");
    }
    Light(int noOfWatts, boolean indicator, String location) {   // (3) Non-default
        this.noOfWatts = noOfWatts;
        this.indicator = indicator;
        this.location  = location;
        System.out.println("Returning from non-default constructor no. 3.");
    }
}

public class DemoThisCall {
    public static void main(String[] args) {                     // (4)
        System.out.println("Creating Light object no. 1.");
        Light light1 = new Light();                              // (5)
        System.out.println("Creating Light object no. 2.");
        Light light2 = new Light(250, true);                     // (6)
        System.out.println("Creating Light object no. 3.");
        Light light3 = new Light(250, true, "attic");            // (7)
    }
}

Output from the program:

Creating Light object no. 1.
Returning from non-default constructor no. 3.
Returning from non-default constructor no. 2.
Returning from default constructor no. 1.
Creating Light object no. 2.
Returning from non-default constructor no. 3.
Returning from non-default constructor no. 2.
Creating Light object no. 3.
Returning from non-default constructor no. 3.

super() Constructor Call

The super() construct is used in a subclass constructor to invoke a constructor in the immediate superclass. This allows the subclass to influence the initialization of its inherited state when an object of the subclass is created. A super() call in the constructor of a subclass will result in the execution of the relevant constructor from the superclass, based on the signature of the call. Since the superclass name is known in the subclass declaration, the superclass constructor invoked is determined by the signature of the parameter list.

A constructor in a subclass can access the class's inherited members directly (i.e., by their simple name). The keyword super can also be used in a subclass constructor to access inherited members via its superclass. One might be tempted to use the super keyword in a constructor to specify initial values of inherited fields. However, the super() construct provides a better solution, using superclass constructors to initialize the inherited state.

In Example 6.8, the non-default constructor at (3) of the class Light has a super() call (with no arguments) at (4). Although the constructor is not strictly necessary, as the compiler will insert one?as explained below?it is included for expositional purposes. The non-default constructor at (6) of class TubeLight has a super() call (with three arguments) at (7). This super() call will match the non-default constructor at (3) of superclass Light. This is evident from the program output.

Example 6.8 super() Constructor Call
class Light {
    // Fields
    private int     noOfWatts;
    private boolean indicator;
    private String  location;

    // Constructors
    Light() {                              // (1) Explicit default constructor
        this(0, false);
        System.out.println(
            "Returning from default constructor no. 1 in class Light");
    }
    Light(int watt, boolean ind) {                              // (2) Non-default
        this(watt, ind, "X");
        System.out.println(
            "Returning from non-default constructor no. 2 in class Light");
    }
    Light(int noOfWatts, boolean indicator, String location) {  // (3) Non-default
        super();                                                // (4)
        this.noOfWatts = noOfWatts;
        this.indicator = indicator;
        this.location  = location;
        System.out.println(
            "Returning from non-default constructor no. 3 in class Light");
    }
}
class TubeLight extends Light {
    // Instance variables
    private int tubeLength;
    private int colorNo;

    TubeLight(int tubeLength, int colorNo) {                    // (5) Non-default
        this(tubeLength, colorNo, 100, true, "Unknown");
        System.out.println(
            "Returning from non-default constructor no. 1 in class TubeLight");
    }

    TubeLight(int tubeLength, int colorNo, int noOfWatts,
              boolean indicator, String location) {             // (6) Non-default
        super(noOfWatts, indicator, location);                  // (7)
        this.tubeLength = tubeLength;
        this.colorNo    = colorNo;
        System.out.println(
            "Returning from non-default constructor no. 2 in class TubeLight");
    }
}
public class Chaining {
    public static void main(String[] args) {
        System.out.println("Creating a TubeLight object.");
        TubeLight tubeLightRef = new TubeLight(20, 5);          // (8)
    }
}

Output from the program:

Creating a TubeLight object.
Returning from non-default constructor no. 3 in class Light
Returning from non-default constructor no. 2 in class TubeLight
Returning from non-default constructor no. 1 in class TubeLight

The super() construct has the same restrictions as the this() construct: if used, the super() call must occur as the first statement in a constructor, and it can only be used in a constructor declaration. This implies that this() and super() calls cannot both occur in the same constructor. The this() construct is used to chain constructors in the same class, and the constructor at the end of such a chain can invoke a superclass constructor using the super() construct. Just as the this() construct leads to chaining of constructors in the same class, the super() construct leads to chaining of subclass constructors to superclass constructors. This chaining behavior guarantees that all superclass constructors are called, starting with the constructor of the class being instantiated, all the way to the top of the inheritance hierarchy, which is always the Object class. Note that the body of the constructors is executed in the reverse order to the call order, as super() can only occur as the first statement in a constructor. This ensures that the constructor from the Object class is completed first, followed by the constructors in the other classes down to the class being instantiated in the inheritance hierarchy. This is called (subclass?superclass) constructor chaining. The output from Example 6.8 clearly illustrates this chain of events when an object of class TubeLight is created.

If a constructor at the end of a this()-chain (which may not be a chain at all if no this() call is invoked) does not have an explicit call to super(), then the call super() (without the parameters) is implicitly inserted to invoke the default constructor of the superclass. In other words, if a constructor has neither a this() nor a super() call as its first statement, then a super() call to the default constructor in the superclass is inserted. The code

class A {
    public A() {}
    // ...
}
class B extends A {
    // no constructors
    // ...
}

class A {
    public A() { super(); }      // (1)
    // ...
}
class B extends A {
    public B() { super(); }      // (2)
    // ...
}

is equivalent to

where the default constructors with calls to the default superclass constructor are inserted in the code.

If a class only defines non-default constructors (i.e., only constructors with parameters), then its subclasses cannot rely on the implicit super() call being inserted. This will be flagged as a compile-time error. The subclasses must then explicitly call a superclass constructor, using the super() construct with the right arguments.

class NeonLight extends TubeLight {
    // Field
    String sign;

    NeonLight() {                               // (1)
        super(10, 2, 100, true, "Roof-top");    // (2) Cannot be commented out.
        sign = "All will be revealed!";
    }
    // ...
}

The previous declaration of the subclass NeonLight provides a constructor at (1). The call at (2) of the constructor in the superclass TubeLight cannot be omitted. If it is omitted, any insertion of a super() call (with no arguments) in this constructor will not match any default constructor in the superclass TubeLight, as this superclass does not provide one. The superclass TubeLight only provides non-default constructors. The class NeonLight will not compile unless an explicit super() call (with valid arguments) is inserted at (2).

If the superclass provides non-default constructors only (i.e., does not have a default constructor), then this has implications for its subclasses. A subclass that relies on its implicit default constructor will fail to compile. This is because the implicit default constructor of the subclass will attempt to call the non-existent default constructor in the superclass. Any constructor in a subclass must explicitly use the super() call, with the appropriate arguments, to invoke a non-default constructor in the superclass. This is because the constructor in the subclass cannot rely on an implicit super() call to the default constructor in the superclass.



     
    ASPTreeView.com
     
    Evaluation has ТИ·µИЧДexpired.
    Info...