Java Method Overriding

Method overriding is a feature in Java that allows a subclass to provide a specific implementation for a method already defined in its superclass. This enables dynamic polymorphism, where the method that gets executed is determined at runtime, based on the type of the object.

When Do We Use Method Overriding?

Method overriding is typically used when:

1. A subclass wants to modify or replace the behavior of a method that it inherits from its superclass.

2. It allows objects of a subclass to be treated as objects of the superclass, but with specific subclass behavior.

Characteristics of Method Overriding

1. Same Method Signature: The overriding method must have the same name, return type, and parameters as the method in the superclass.

2. Inheritance: Overriding happens in the context of inheritance. A subclass overrides methods inherited from its superclass.

3. Access Modifiers: The access modifier of the overriding method should be the same or more permissive than the method in the superclass. For example, if the superclass method is protected, the subclass can override it with protected or public, but not private.

4. Return Type: The return type of the overriding method can be the same or a subtype (this is known as covariant return types).

5. Use of @Override Annotation: The @Override annotation is optional but highly recommended. It helps prevent mistakes and ensures that you are indeed overriding a method from the superclass.

Example 1: Basic Method Overriding

Let’s start with a simple example where a superclass method is overridden in a subclass.


//File Name is Main.java
// Superclass: Animal
class Animal {
    // Method in superclass
    public void sound() {
        System.out.println("Animals make different sounds");
    }
}

// Subclass: Dog
class Dog extends Animal {
    // Overriding the sound method
    @Override
    public void sound() {
        System.out.println("Dog barks");
    }
}

// Subclass: Cat
class Cat extends Animal {
    // Overriding the sound method
    @Override
    public void sound() {
        System.out.println("Cat meows");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myDog = new Dog();
        Animal myCat = new Cat();
        
        myDog.sound();  // Dog barks
        myCat.sound();  // Cat meows
    }
}

Explanation:

  1. Superclass Animal has a method sound() that prints a general message.
  2. Subclass Dog overrides sound() to provide its own implementation, which prints “Dog barks”.
  3. Subclass Cat also overrides sound() to provide its own implementation, which prints “Cat meows”.
  4. In the Main class, we create instances of Dog and Cat, but we assign them to variables of type Animal (this is polymorphism).
  5. When we call sound() on myDog and myCat, the overridden methods in Dog and Cat are executed.

Example 2: Method Overriding with Covariant Return Types

A subclass can override a method and return a type that is a subclass of the return type declared in the superclass. This is called covariant return type.


//File Name is Main.java
// Superclass: Animal
class Animal {
    public Animal makeSound() {
        System.out.println("Animal makes a sound");
        return this;
    }
}

// Subclass: Dog
class Dog extends Animal {
    @Override
    public Dog makeSound() {
        System.out.println("Dog barks");
        return this;  // Returning the subclass type (Dog)
    }
}

// Subclass: Cat
class Cat extends Animal {
    @Override
    public Cat makeSound() {
        System.out.println("Cat meows");
        return this;  // Returning the subclass type (Cat)
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myDog = new Dog();
        Animal myCat = new Cat();
        
        myDog.makeSound();  // Dog barks
        myCat.makeSound();  // Cat meows
    }
}

Explanation:

  1. The makeSound() method in Animal returns an Animal type.
  2. In the subclasses Dog and Cat, the makeSound() method overrides the one in Animal and returns the specific subclass type (Dog and Cat, respectively). This is allowed because Dog and Cat are subclasses of Animal, thus returning a subclass type is allowed (covariant return type).
  3. Even though the reference variable is of type Animal, the correct subclass method (Dog or Cat) is called.

Example 3: Overriding and Using super Keyword

In case the subclass wants to call the method from the superclass, it can use the super keyword. This is useful when we want to add additional behavior to the method of the superclass while still keeping the original behavior.


class Animal {
    public void sound() {
        System.out.println("Animal makes a sound");
    }
}

class Cat extends Animal {
    @Override
    public void sound() {
        super.sound();  // Calling the superclass method
        System.out.println("Cat meows");
    }
}

public class Main {
    public static void main(String[] args) {
        Cat myCat = new Cat();
        myCat.sound();  // Output: Animal makes a sound
                        //         Cat meows
    }
}

Explanation:

  1. In this case, the Cat class overrides the sound() method and first calls the sound() method from the Animal class using super.sound().
  2. This allows the subclass to extend the functionality of the superclass method rather than completely replace it.

Restrictions on Method Overriding

There are certain methods that cannot be overridden

1. Final methods: Methods marked as final cannot be overridden.

2. Static methods: Static methods are not inherited, so they cannot be overridden.

3. Private methods: Private methods are not inherited by the subclass, so they cannot be overridden.