7.6. Method Overrides

It is common for a child class to change a behavior (method) inherited from a parent. In Java, this is called a method override.

Method overrides can occur in two ways:

  1. The child changes the code in the inherited method without using any of the code in the parent’s method (Example 1 below).

  2. The child adds to the code it inherited from the parent. Here, the inherited code in the method is still used. (Example 2 below).

Example 1 - Overwriting all Inherited Code

The video below illustrates how to override an inherited method. The starter code for the example in the video can be found under the person subdirectory of the cs1302-inheritance directory you downloaded at the beginning of this tutorial. For convenience, we have included the UML Class Diagram for the code we created in the earlier section below (dropdown to view):

UML Class Diagram for the Person Example (from earlier section)

   hide circle
   set namespaceSeparator none
   skinparam classAttributeIconSize 0

   package "cs1302.identity" {
        Driver -up-> Person: uses
        Driver -right-> Employee: uses
        Person <|-- Employee: extends (is-a)

        class Person {
           -name: String
           -dateOfBirth: LocalDate
           +<<new>> Person(name: String, dateOfBirth: LocalDate)
           +getName(): String
           +getDateOfBirth(): LocalDate
           +computeAge(): int
           +<<override>> toString(): String
        }

        class Driver {
           {static} +main(args: String[]): void
        }

        class Employee {
           -id: long
           -dateOfHire: LocalDate

           +<<new>> Employee(id: long, name: String,
           \t\t\t    dateOfBirth: LocalDate, \n\t\t\t    dateOfHire: LocalDate)
           +getId(): long
           +computeAge(): int
           +<<override>> toString(): String
        }

        hide Driver fields
   }

Follow along with this video using the code you modified earlier in the chapter:

IMAGE ALT TEXT

Important

If a method is overridden in a child class, the method closest to the type of the object will be called when the code is run.

Learn by Experimenting!

You may have noticed the @Override above each toString() method. This is an example of an annotation. In Java, annotations are used to provide extra information to the compiler. In this case, the @Override is an optional annotation that lets the compiler know your intent is to override. Try the following scenarios:

  1. Omit the @Override, then recompile.

  2. Omit the @Override and misspell the toString method name, then recompile.

  3. Include the @Override and misspell the toString method name,

    then recompile.

Notice the error that occurs in the third situation that did not occur in the second. This is the primary purpose of the annotation. Although optional, it allowed you to tell the compiler that your intent is to override. If the compiler sees the @Override annotation, then it checks to make sure it’s an override and will let you know if you made a mistake!

Example 2 - Adding to Inherited Code

It is also possible to have the child class build on the action performed in the parent class. In other words, the child can perform the same action as the parent and then do additional actions in the overridden method. In these scenarios, it is important to avoid redundant code and simply call the parent’s version of the method using super.

Let’s add a few methods (and a Cat class) to our animal hierarchy from the previous section. Take a moment to review the updated UML Class Diagram below (Driver class omitted to save space):

hide circle
set namespaceSeparator none
skinparam classAttributeIconSize 0

package "cs1302.animal" {
   Animal <|-- Dog
   Animal <|-- Cat

   class Animal {
        -name: String
        -genus: String
        -species: String

        +<<new>> Animal(name: String, genus: String, species: String)
        +getGenus(): String
        +getSpecies(): String
        +getName(): String
        +makeSound(): void
        +describe(): void
   }

   class Dog {
        -breed: String

        +<<new>> Dog(name: String, breed: String)
        +<<override>>makeSound() : void
        +<<override>>describe() : void

        +getBreed(): String
   }

   class Cat {
        +Cat(name : String)
        +<<override>>makeSound() : void
        +<<override>>describe() : void
   }

   hide Cat fields
}

Notice that both Cat and Dog override the method describe from the parent class. Now, let’s consider the following implementation for the describe method in the Animal class:

This code is in the Animal class.
/**
 * Prints a description of the {@Animal}.
 */
 public void describe() {
    System.out.println("This is an animal named " + this.name + ".");
 } // describe

From within the Animal class, the description has to be a bit vague since we are talking about a generic animal. However, as we move down the hierarchy (overriding the describe method as we go), the child classes can add to the parent’s output to print a more accurate description.

The implementation of describe in the Cat class could leverage the work that’s already done in the parent (Animal) class without needing to repeat the code:

This code is in the Cat class.
/**
 * Prints a description of the {@Cat}.
 */
 @Override
 public void describe() {
    super.describe(); // calls the describe method of the parent.

    System.out.println(this.getName() + " is an agile cat."); // adds to it
 } // describe

Test Yourself

What do you expect would be output after running the main method below? Assume that the method has access to both the Animal and Cat classes above.

Notice that the data types of the variables are not the same.

public static void main(String[] args) {
   // 1.
   Cat cat = new Cat("Whiskers");
   System.out.println("\nCat:");
   cat.describe();

   // 2.
   Animal cat2 = new Cat("Garfield");
   System.out.println("\nCat:");
   cat2.describe();
} // main
Test Yourself Solution (open after attempting the question above)

The output from the above code should be:

Cat:
There is an animal named Whiskers.
Whiskers is an agile cat.

Cat:
There is an animal named Garfield.
Garfield is an agile cat.

Note that both examples call the describe method in the Cat class because both are objects of type Cat. Also, when we call the describe method in the Cat class, we first see the output written in the Animal class because we call the parent’s method using super.