6.2 Overriding and Hiding Members

Instance Method Overriding

Under certain circumstances, a subclass may override non-static methods defined in the superclass that would otherwise be inherited. When the method is invoked on an object of the subclass, it is the new method implementation in the subclass that is executed. The overridden method in the superclass is not inherited by the subclass, and the new method in the subclass must uphold the following rules of method overriding:

  • The new method definition must have the same method signature (i.e., method name and parameters) and the same return type.

    Whether parameters in the overriding method should be final is at the discretion of the subclass (see Section 3.22, p. 94). A method's signature does not encompass the final modifier of parameters, only their types and order.

  • The new method definition cannot narrow the accessibility of the method, but it can widen it (see Section 4.9, p. 137).

  • The new method definition can only specify all or none, or a subset of the exception classes (including their subclasses) specified in the throws clause of the overridden method in the superclass (see Section 5.9, p. 201).

These requirements also apply to interfaces, where a subinterface can override method prototypes from its superinterfaces (see Section 6.4, p. 251).

In Example 6.3, the new definition of the getBill() method at (5) in the subclass TubeLight has the same signature and the same return type as the method at (2) in the superclass Light. The new definition specifies a subset of the exceptions (ZeroHoursException) thrown by the overridden method (exception class Invalid HoursException is a superclass of NegativeHoursException and ZeroHoursException). The new definition also widens the accessibility (public) from what it was in the overridden definition (protected). The overriding method also declares the parameter to be final. Invocation of the method getBill() on an object of subclass TubeLight using references of the subclass and the superclass at (12) and (13) respectively, results in the new definition at (5) being executed. Invocation of the method getBill() on an object of superclass Light using a reference of the superclass at (14), results in the overridden definition at (2) being executed.

Example 6.3 Overriding, Overloading, and Hiding
// Exceptions
class InvalidHoursException extends Exception {}
class NegativeHoursException extends InvalidHoursException {}
class ZeroHoursException extends InvalidHoursException {}
class Light {

    protected String billType = "Small bill";     // (1)

    protected double getBill(int noOfHours)
              throws InvalidHoursException {      // (2)
        if (noOfHours < 0)
            throw new NegativeHoursException();
        double smallAmount = 10.0,
               smallBill = smallAmount * noOfHours;
        System.out.println(billType + ": " + smallBill);
        return smallBill;
    }

    public static void printBillType() {          // (3)
        System.out.println("Small bill");
    }

}

class TubeLight extends Light {

    public static String billType = "Large bill"; // (4) Hiding static field.

    public double getBill(final int noOfHours)
           throws ZeroHoursException {     // (5) Overriding instance method.
        if (noOfHours == 0)
            throw new ZeroHoursException();
        double largeAmount = 100.0,
               largeBill = largeAmount * noOfHours;
        System.out.println(billType + ": " + largeBill);
        return largeBill;
    }

    public static void printBillType() {          // (6) Hiding static method.
        System.out.println(billType);
    }

    public double getBill() {                     // (7) Overloading method.
        System.out.println("No bill");
        return 0.0;
    }
}

public class Client {
    public static void main(String[] args)
            throws InvalidHoursException {        // (8)

        TubeLight tubeLight = new TubeLight();    // (9)
        Light     light1    = tubeLight;          // (10) Aliases.
        Light     light2    = new Light();        // (11)

        System.out.println("Invoke overridden instance method:");
        tubeLight.getBill(5);                     // (12) Invokes method at (5)
        light1.getBill(5);                        // (13) Invokes method at (5)
        light2.getBill(5);                        // (14) Invokes method at (2)
        System.out.println("Access hidden field:");
        System.out.println(tubeLight.billType);   // (15) Accesses field at (4)
        System.out.println(light1.billType);      // (16) Accesses field at (1)
        System.out.println(light2.billType);      // (17) Accesses field at (1)

        System.out.println("Invoke hidden static method:");
        tubeLight.printBillType();                // (18) Invokes method at (6)
        light1.printBillType();                   // (19) Invokes method at (3)
        light2.printBillType();                   // (20) Invokes method at (3)

        System.out.println("Invoke overloaded method:");
        tubeLight.getBill();                      // (21) Invokes method at (7)
    }
}

Output from the program:

Invoke overridden instance method:
Large bill: 500.0
Large bill: 500.0
Small bill: 50.0
Access hidden field:
Large bill
Small bill
Small bill
Invoke hidden static method:
Large bill
Small bill
Small bill
Invoke overloaded method:
No bill

A subclass must use the keyword super in order to invoke an overridden method in the superclass (see p. 238).

An instance method in a sublass cannot override a static method in the superclass. The compiler will flag this as an error. A static method is class-specific and not part of any object, while overriding methods are invoked on behalf of objects of the subclass. However, a static method in a subclass can hide a static method in the superclass (see below).

A final method cannot be overridden because the modifier final prevents method overriding. An attempt to override a final method will result in a compile-time error. However, an abstract method requires the non-abstract subclasses to override the method, in order to provide an implementation.

Accessibility modifier private for a method means that the method is not accessible outside the class in which it is defined; therefore, a subclass cannot override it. However, a subclass can give its own definition of such a method, which may have the same signature as the method in its superclass.

Field Hiding

A subclass cannot override fields of the superclass, but it can hide them. The subclass can define fields with the same name as in the superclass. If this is the case, the fields in the superclass cannot be accessed in the subclass by their simple names; therefore, they are not inherited by the subclass. Code in the subclass can use the keyword super to access such members, including hidden fields. A client can use a reference of the superclass to access members that are hidden in the subclass, as explained below. Of course, if the hidden field is static, it can also be accessed by the superclass name.

The following distinction between invoking instance methods on an object and accessing fields of an object must be noted. When an instance method is invoked on an object using a reference, it is the class of the current object denoted by the reference, not the type of the reference, that determines which method implementation will be executed. In Example 6.3 at (12), (13), and (14), this is evident from invoking the overridden method getBill(): the method from the class corresponding to the current object is executed, regardless of the reference type. When a field of an object is accessed using a reference, it is the type of the reference, not the class of the current object denoted by the reference, that determines which field will actually be accessed. In Example 6.3 at (15), (16), and (17), this is evident from accessing the hidden field billType: the field accessed is declared in the class corresponding to the reference type, regardless of the object denoted by the reference.

In contrast to method overriding where an instance method cannot override a static method, there are no such restrictions on the hiding of fields. The field billType is static in the subclass, but not in the superclass. The type of the fields need not be the same either, it is only the field name that matters in the hiding of fields.

Static Method Hiding

A static method cannot override an inherited instance method, but it can hide a static method if the exact requirements for overriding instance methods are fulfilled. A hidden superclass static method is not inherited. The compiler will flag an error if the signatures are the same but the other requirements regarding return type, throws clause, and accessibility are not met. If the signatures are different, the method name is overloaded, not hidden.

The binding of a method call to a method implementation is done at compile time if the method is static or final (private methods are implicitly final). Example 6.3 illustrates invocation of static methods. Analogous to accessing fields, the method invoked in (18), (19), and (20) is determined by the class of the reference. In (18) the class type is TubeLight, therefore, the static method printBillType() at (6) in this class is invoked. In (19) and (20) the class type is Light and the hidden static method printBillType() at (3) in that class is invoked. This is borne out by the output from the program.

A hidden static method can, of course, be invoked by using the superclass name in the subclass declaration. Additionally, the keyword super can be used in non-static code in the subclass declaration to invoke hidden static methods (see p. 238).

Overriding vs. Overloading

Method overriding should not be confused with method overloading (see Section 4.3, p. 116). Method overriding requires the same method signature (name and parameters) and the same return type. Only non-final instance methods in the superclass that are directly accessible from the subclass are eligible for overriding. Overloading occurs when the method names are the same, but the parameter lists differ. Therefore, to overload methods, the parameters must differ in type, order, or number. As the return type is not a part of the signature, having different return types is not enough to overload methods.

A method can be overloaded in the class it is defined in or in a subclass of its class. Invoking an overridden method in the superclass from a subclass requires special syntax (e.g., the keyword super). This is not necessary for invoking an overloaded method in the superclass from a subclass. If the right kinds of arguments are passed in the method call occurring in the subclass, the overloaded method in the superclass will be invoked. In Example 6.3, the method getBill() at (2) in class Light is overridden in class TubeLight at (5) and overloaded at (7). When invoked at (21), the definition at (7) is executed.

Method Overloading Resolution

Example 6.4 illustrates how parameter resolution is done to choose the right implementation for an overloaded method. The method testIfOn() is overloaded at (1) and (2) in class OverloadResolution. The call client.testIfOn(tubeLight) at (3) satisfies the parameter lists in both the implementations given at (1) and (2), as the reference tubeLight, which denotes an object of class TubeLight, can also be assigned to a reference of its superclass Light. The most specific method, (2), is chosen, resulting in false being written on the terminal. The call client.testIfOn(light) at (4) only satisfies the parameter list in the implementation given at (1), resulting in true being written on the terminal.

Example 6.4 Overloaded Method Resolution
class Light { /* ... */ }

class TubeLight extends Light { /* ... */ }

public class OverloadResolution {
    boolean testIfOn(Light aLight)         { return true; }    // (1)
    boolean testIfOn(TubeLight aTubeLight) { return false; }   // (2)
    public static void main(String[] args) {

        TubeLight tubeLight = new TubeLight();
        Light     light     = new Light();

        OverloadResolution client = new OverloadResolution();
        System.out.println(client.testIfOn(tubeLight));// (3) ==> method at (2)
        System.out.println(client.testIfOn(light));    // (4) ==> method at (1)

    }
}

Output from the program:

false
true

Object Reference super

The this reference is available in non-static code and refers to the current object. When an instance method is invoked, the this reference denotes the object on which the method is called (see Section 4.3, p. 114). The keyword super can also be used in non-static code (e.g., in the body of an instance method), but only in a subclass, to access fields and invoke methods from the superclass (see Table 4.1, p.122). The keyword super provides a reference to the current object as an instance of its superclass. In method invocations with super, the method from the superclass is simply invoked regardless of the actual type of the object or whether the current class overrides the method. It is typically used to invoke methods that are overridden and to access members that are hidden in the subclass. Unlike the this keyword, the super keyword cannot be used as an ordinary reference. For example, it cannot be assigned to other references or cast to other reference types.

In Example 6.5, the method demonstrate() at (9) in class NeonLight makes use of the super keyword to access members higher up in its inheritance hierarchy. This is the case when the banner() method is invoked at (10). This method is defined at (4) in class Light and not in the immediate superclass of subclass NeonLight. The overridden method getBill() and its overloaded version at (6) and (8) in class TubeLight are invoked, using super at (12) and (11), respectively.

Class NeonLight is a subclass of class TubeLight, which is a subclass of class Light, which has a field named billType and a method named getBill defined at (1) and (2), respectively. One might be tempted to use the syntax super.super.getBill(20) in subclass NeonLight to invoke this method, but this is not a valid construct. One might also be tempted to cast the this reference to the class Light and try again as shown at (13). The output shows that the method getBill() at (6) in class TubeLight was executed, not the one from class Light. The reason is that a cast only changes the type of the reference (in this case to Light), not the class of the object (which is still NeonLight). Method invocation is determined by the class of the current object, resulting in the inherited method getBill() in class TubeLight being executed. There is no way to invoke the method getBill() in class Light from the subclass NeonLight.

At (14) the keyword super is used to access the field billType at (5) in class TubeLight. At (15) the field billType from class Light is accessed successfully by casting the this reference, because it is the type of the reference that determines the field accessed. From non-static code in a subclass, it is possible to directly access fields in a class higher up the inheritance hierarchy, by casting the this reference. However, it is futile to cast the this reference to invoke instance methods in a class higher up the inheritance hierarchy, as illustrated above in the case of the overridden method getBill().

Finally, calls to static methods at (16) and (17) using super and this references, exhibit runtime behavior analagous to accessing fields as discussed earlier.

Example 6.5 Using super Keyword
// Exceptions
class InvalidHoursException extends Exception {}
class NegativeHoursException extends InvalidHoursException {}
class ZeroHoursException extends InvalidHoursException {}

class Light {

    protected String billType  = "Small bill";       // (1)

    protected double getBill(int noOfHours)
              throws InvalidHoursException {         // (2)
        if (noOfHours < 0)
            throw new NegativeHoursException();
        double smallAmount = 10.0,
               smallBill = smallAmount * noOfHours;
        System.out.println(billType + ": " + smallBill);
        return smallBill;
    }

    public static void printBillType() {             // (3)
        System.out.println("Small bill");
    }

    public void banner() {                           // (4)
        System.out.println("Let there be light!");
    }
}

class TubeLight extends Light {

    public static String billType = "Large bill";    // (5) Hiding static field.

    public double getBill(final int noOfHours)
           throws ZeroHoursException {        // (6) Overriding instance method.
        if (noOfHours == 0)
            throw new ZeroHoursException();
        double largeAmount = 100.0,
               largeBill = largeAmount * noOfHours;
        System.out.println(billType + ": " + largeBill);
        return largeBill;
    }

    public static void printBillType() {             // (7) Hiding static method.
        System.out.println(billType);
    }

    public double getBill() {                        // (8) Overloading method.
        System.out.println("No bill");
        return 0.0;
    }
}

class NeonLight extends TubeLight {
    // ...
    public void demonstrate()
            throws InvalidHoursException {           // (9)

        super.banner();                              // (10) Invokes method at (4)
        super.getBill();                             // (11) Invokes method at (8)
        super.getBill(20);                           // (12) Invokes method at (6)
        ((Light) this).getBill(20);                  // (13) Invokes method at (6)
        System.out.println(super.billType);          // (14) Accesses field at (5)
        System.out.println(((Light) this).billType); // (15) Accesses field at (1)
        super.printBillType();                       // (16) Invokes method at (7)
        ((Light) this).printBillType();              // (17) Invokes method at (3)
    }
}

public class Client {
    public static void main(String[] args)
                       throws InvalidHoursException {
        NeonLight neonRef = new NeonLight();
        neonRef.demonstrate();
    }
}

Output from the program:

Let there be light!
No bill
Large bill: 2000.0
Large bill: 2000.0
Large bill
Small bill
Large bill
Small bill