Overriding Instance Methods – Object-Oriented Programming

Overriding Instance Methods – Object-Oriented Programming

Overriding Instance Methods

Under certain circumstances, a subclass can override instance methods from its superclass. Overriding such a method allows the subclass to provide its own implementation of the method. The overridden method in the superclass is not inherited by the subclass. When the method is invoked on an object of the subclass, it is the method implementation in the subclass that is executed. The new method in the subclass must abide by the following rules of method overriding:

  • The new method definition in the subclass must have the same method signature. In other words, the method name, and the types and the number of parameters, including their order, must be the same as in the overridden method of the superclass.

Whether parameters in the overriding method should be final is at the discretion of the subclass. A method’s signature does not comprise the final modifier of parameters, only their types and order.

  • The return type of the overriding method can be a subtype of the return type of the overridden method (called covariant return, p. 201).
  • The new method definition cannot narrow the accessibility of the method, but it can widen it.
  • The new method definition can throw either all or none, or a subset of the checked exceptions (including their subclasses) that are specified in the throws clause of the overridden method in the superclass (§7.5, p. 388)—that is, the overriding method may not throw any checked exception that is wider than the checked exceptions thrown by the overridden method. However, the overriding method may throw any unchecked exceptions.

The criteria for overriding methods also apply to interfaces, where a subinterface can override abstract and default method declarations from its superinterfaces (p. 237).

Figure 5.2 Inheritance Hierarchy for Example 5.2

The canonical examples of method overriding in Java are the equals(), hashCode(), and toString() methods of the Object class. The wrapper classes and the String class all override these methods (Chapter 8, p. 423). Overriding these methods in user-defined classes is thoroughly covered in connection with object comparison (Chapter 14, p. 741).

Example 5.2 illustrates overriding, overloading, and hiding of members in a class. Figure 5.2 gives an overview of the two main classes in Example 5.2. The new definition of the energyCost() method at (7) 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 (the exception class InvalidHoursException 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 declares the parameter to be final, but this has no bearing on overriding the method.

Click here to view code image

// The overridden method in the superclass Light:
protected double energyCost(int noOfHours)      // (2) Instance method
    throws InvalidHoursException { … }

// The overriding method in the subclass TubeLight:
@Override
public double energyCost(final int noOfHours)   // (7) Overriding instance
    throws ZeroHoursException { … }           //     method at (2).

The astute reader will have noticed the @Override annotation preceding the method definition at (7). The compiler will now report an error if the method definition at (7) does not override an inherited method. The annotation helps to ensure that the method definition overrides the inherited method, rather than overloading another method silently.

Invocation of the method energyCost() on an object of subclass TubeLight using references of the subclass and the superclass at (15) and (16), respectively, results in the new definition at (7) being executed, since both references are aliases of the TubeLight object created at (12).

Click here to view code image

tubeLight.energyCost(50);                     // (15) Invokes method at (7).
light1.energyCost(50);                        // (16) Invokes method at (7).

Not surprisingly, the invocation of the method energyCost() on an object of superclass Light, using a reference of the superclass at (17), results in the overridden definition at (2) being executed:

Click here to view code image

light2.energyCost(50);                        // (17) Invokes method at (2).

Example 5.2 Overriding, Overloading, and Hiding

Click here to view code image

// File: Client2.java
// Exceptions
class InvalidHoursException extends Exception {}
class NegativeHoursException extends InvalidHoursException {}
class ZeroHoursException extends InvalidHoursException {}
class Light {
  protected String lightType = “Generic Light”;   // (1) Instance field
  protected double energyCost(int noOfHours)      // (2) Instance method
      throws InvalidHoursException {
    System.out.print(“>> Light.energyCost(int): “);
    if (noOfHours < 0)
      throw new NegativeHoursException();
    double cost = 00.20 * noOfHours;
    System.out.println(“Energy cost for ” + lightType + “: ” + cost);
    return cost;
  }
  public Light makeInstance() {                   // (3) Instance method
    System.out.print(“>> Light.makeInstance(): “);
    return new Light();
  }
  public void showSign() {                        // (4) Instance method
    System.out.print(“>> Light.showSign(): “);
    System.out.println(“Let there be light!”);
  }
  public static void printLightType() {           // (5) Static method
    System.out.print(“>> Static Light.printLightType(): “);
    System.out.println(“Generic Light”);
  }
}
//______________________________________________________________________________
class TubeLight extends Light {
  public static String lightType = “Tube Light”;  // (6) Hiding field at (1).
  @Override
  public double energyCost(final int noOfHours)   // (7) Overriding instance
      throws ZeroHoursException {                 //     method at (2).
    System.out.print(“>> TubeLight.energyCost(int): “);
    if (noOfHours == 0)
      throw new ZeroHoursException();
    double cost = 00.10 * noOfHours;
    System.out.println(“Energy cost for ” + lightType + “: ” + cost);
    return cost;
  }
  public double energyCost() {          // (8) Overloading method at (7).
    System.out.print(“>> TubeLight.energyCost(): “);
    double flatrate = 20.00;
    System.out.println(“Energy cost for ” + lightType + “: ” + flatrate);
    return flatrate;
  }
  @Override
  public TubeLight makeInstance() {     // (9) Overriding instance method at (3).
    System.out.print(“>> TubeLight.makeInstance(): “);
    return new TubeLight();
  }
  public static void printLightType() { // (10) Hiding static method at (5).
    System.out.print(“>> Static TubeLight.printLightType(): “);
    System.out.println(lightType);
  }
}
//______________________________________________________________________________
public class Client2 {
  public static void main(String[] args)          // (11)
      throws InvalidHoursException {
    TubeLight tubeLight = new TubeLight();        // (12)
    Light     light1    = tubeLight;              // (13) Aliases.
    Light     light2    = new Light();            // (14)
    System.out.println(“Invoke overridden instance method:”);
    tubeLight.energyCost(50);                     // (15) Invokes method at (7).
    light1.energyCost(50);                        // (16) Invokes method at (7).
    light2.energyCost(50);                        // (17) Invokes method at (2).
    System.out.println(
        “\nInvoke overridden instance method with covariant return:”);
    System.out.println(
        light2.makeInstance().getClass());        // (18) Invokes method at (3).
    System.out.println(
        tubeLight.makeInstance().getClass());     // (19) Invokes method at (9).
    System.out.println(“\nAccess hidden field:”);
    System.out.println(tubeLight.lightType);      // (20) Accesses field at (6).
    System.out.println(light1.lightType);         // (21) Accesses field at (1).
    System.out.println(light2.lightType);         // (22) Accesses field at (1).
    System.out.println(“\nInvoke hidden static method:”);
    tubeLight.printLightType();                   // (23) Invokes method at (10).
    light1.printLightType();                      // (24) Invokes method at (5).
    light2.printLightType();                      // (25) Invokes method at (5).
    System.out.println(“\nInvoke overloaded method:”);
    tubeLight.energyCost();                       // (26) Invokes method at (8).
  }
}

Output from the program:

Click here to view code image

Invoke overridden instance method:
>> TubeLight.energyCost(int): Energy cost for Tube Light: 5.0
>> TubeLight.energyCost(int): Energy cost for Tube Light: 5.0
>> Light.energyCost(int): Energy cost for Generic Light: 10.0
Invoke overridden instance method with covariant return:
>> Light.makeInstance(): class Light
>> TubeLight.makeInstance(): class TubeLight
Access hidden field:
Tube Light
Generic Light
Generic Light
Invoke hidden static method:
>> Static TubeLight.printLightType(): Tube Light
>> Static Light.printLightType(): Generic Light
>> Static Light.printLightType(): Generic Light
Invoke overloaded method:
>> TubeLight.energyCost(): Energy cost for Tube Light: 20.0

Here are a few more facts to note about overriding.

  • A subclass must use the keyword super to invoke an overridden method in the superclass.
  • 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. An abstract method, in contrast, requires the non-abstract subclasses to override the method so as to provide an implementation. Abstract and final methods are discussed in §5.4, p. 224, and §5.5, p. 226, respectively.
  • The access 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.
  • A subclass within the same package as the superclass can override any non-final methods declared in the superclass. However, a subclass in a different package can override only the non-final methods that are declared as either public or protected in the superclass.
  • An instance method in a subclass cannot override a static method in the superclass. The compiler will flag such an attempt 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, as we shall see (p. 203), but it cannot hide an instance method in the superclass.
  • Constructors, since they are not inherited, cannot be overridden.