Module: Inheritance and Polymorphism

Method Overriding

Java Core: Inheritance and Polymorphism - Method Overriding

Method overriding is a core concept in object-oriented programming, specifically within the realms of inheritance and polymorphism. It allows a subclass to provide a specific implementation of a method that is already defined in its superclass. This is a powerful mechanism for customizing behavior and achieving polymorphism.

What is Method Overriding?

  • Definition: Method overriding occurs when a subclass provides its own implementation of a method that is already defined in its superclass. The method must have the same name, return type, and parameter list (signature) as the method in the superclass.
  • Purpose: To allow a subclass to modify or extend the behavior inherited from its superclass. It's about replacing the superclass's implementation with a more specialized one.
  • Key Requirements:
    • Inheritance: Overriding can only happen when a class inherits from another class (using the extends keyword).
    • Same Signature: The method in the subclass must have the exact same name, return type, and parameter list as the method in the superclass. The order and types of parameters are crucial.
    • Accessibility: The overriding method in the subclass must have at least the same access modifier as the overridden method in the superclass. (e.g., public can override protected, but protected cannot override public).
    • @Override Annotation (Recommended): Using the @Override annotation is highly recommended. It tells the compiler that you intend to override a method. If the method doesn't actually override anything (e.g., a typo in the method name), the compiler will generate an error, helping you catch mistakes early.

Example

// Superclass (Base Class)
class Animal {
    public void makeSound() {
        System.out.println("Generic animal sound");
    }
}

// Subclass (Derived Class)
class Dog extends Animal {
    @Override // Good practice!
    public void makeSound() {
        System.out.println("Woof!");
    }
}

// Subclass (Derived Class)
class Cat extends Animal {
    @Override // Good practice!
    public void makeSound() {
        System.out.println("Meow!");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = new Dog();
        Cat cat = new Cat();

        animal.makeSound(); // Output: Generic animal sound
        dog.makeSound();    // Output: Woof!
        cat.makeSound();    // Output: Meow!

        // Polymorphism in action
        Animal animalDog = new Dog(); // Upcasting
        animalDog.makeSound(); // Output: Woof! (Dog's implementation)

        Animal animalCat = new Cat(); // Upcasting
        animalCat.makeSound(); // Output: Meow! (Cat's implementation)
    }
}

Explanation:

  1. Animal is the superclass with a makeSound() method.
  2. Dog and Cat are subclasses of Animal.
  3. Both Dog and Cat override the makeSound() method, providing their own specific implementations.
  4. In the main method, when makeSound() is called on a Dog object, the Dog's implementation is executed. Similarly, for Cat.
  5. The lines Animal animalDog = new Dog(); and Animal animalCat = new Cat(); demonstrate polymorphism. We're assigning a Dog object to an Animal reference. When animalDog.makeSound() is called, the actual object type (Dog) determines which makeSound() method is executed. This is runtime polymorphism.

super Keyword

The super keyword can be used within the overridden method in the subclass to call the superclass's implementation of the method. This is useful if you want to extend the superclass's behavior rather than completely replacing it.

class Animal {
    public void makeSound() {
        System.out.println("Generic animal sound");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        super.makeSound(); // Call the superclass's makeSound()
        System.out.println("Woof!"); // Add Dog-specific behavior
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.makeSound(); // Output: Generic animal sound\nWoof!
    }
}

In this example, the Dog's makeSound() method first calls the Animal's makeSound() method using super.makeSound(), and then adds its own "Woof!" output.

Why Use Method Overriding?

  • Polymorphism: Enables you to treat objects of different classes in a uniform way through a common interface (the superclass).
  • Code Reusability: Inherit common behavior from the superclass and customize it in subclasses.
  • Flexibility and Extensibility: Easily add new subclasses with specialized behavior without modifying the superclass.
  • Maintainability: Changes to the superclass's behavior can be propagated to subclasses (unless overridden).

Method Overriding vs. Method Overloading

It's important to distinguish method overriding from method overloading:

Feature Method Overriding Method Overloading
Relationship Occurs in inheritance (subclass-superclass) Occurs within the same class
Purpose To provide a specific implementation in a subclass To provide multiple methods with the same name but different parameters
Parameters Same signature (name, return type, parameters) Different parameter lists (number, types, order)
Return Type Must be the same Can be different
Keyword @Override (recommended) None

In summary, method overriding is a fundamental concept in object-oriented programming that allows subclasses to customize inherited behavior, enabling polymorphism and promoting code reusability and flexibility. Using the @Override annotation is a best practice to ensure correctness and maintainability.