Skip to content

7.2. Method Overriding

Method overriding occurs when a subclass provides a specific implementation of a method that is already defined in its parent class. It enables runtime polymorphism.

Overriding Rules

  1. Same method name and parameters as parent class
  2. Return type should be the same or covariant (subtype)
  3. Access level cannot be more restrictive
  4. Cannot override final, private, or static methods

Basic Overriding Example

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

    public void eat() {
        System.out.println("Animal is eating");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks: Woof! Woof!");
    }

    @Override
    public void eat() {
        System.out.println("Dog is eating dog food");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Cat meows: Meow! Meow!");
    }

    @Override
    public void eat() {
        System.out.println("Cat is eating cat food");
    }
}

Using @Override Annotation

java
class BankAccount {
    protected double balance;

    public BankAccount(double balance) {
        this.balance = balance;
    }

    public void withdraw(double amount) {
        if (amount <= balance) {
            balance -= amount;
            System.out.println("Withdrawn: $" + amount);
        }
    }

    public double getBalance() {
        return balance;
    }
}

class SavingsAccount extends BankAccount {
    private double interestRate;

    public SavingsAccount(double balance, double interestRate) {
        super(balance);
        this.interestRate = interestRate;
    }

    @Override  // Compiler checks if this actually overrides a method
    public void withdraw(double amount) {
        if (amount <= balance - 100) {  // Maintain minimum balance
            balance -= amount;
            System.out.println("Withdrawn: $" + amount);
        } else {
            System.out.println("Insufficient funds (minimum balance required)");
        }
    }

    public void applyInterest() {
        balance += balance * interestRate;
        System.out.println("Interest applied. New balance: $" + balance);
    }
}

Covariant Return Types

java
class Shape {
    public Shape createNew() {
        return new Shape();
    }

    public void draw() {
        System.out.println("Drawing a shape");
    }
}

class Circle extends Shape {
    @Override
    public Circle createNew() {  // Covariant return type
        return new Circle();
    }

    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }
}

Access Modifiers in Overriding

java
class Base {
    protected void show() {
        System.out.println("Base show");
    }

    public void display() {
        System.out.println("Base display");
    }
}

class Derived extends Base {
    @Override
    public void show() {  // Can increase visibility (protected → public)
        System.out.println("Derived show");
    }

    // @Override
    // protected void display() {  // ERROR: Cannot reduce visibility
    //     System.out.println("Derived display");
    // }
}

Complete Overriding Example

java
// Employee hierarchy with method overriding
class Employee {
    protected String name;
    protected double baseSalary;

    public Employee(String name, double baseSalary) {
        this.name = name;
        this.baseSalary = baseSalary;
    }

    public double calculateBonus() {
        return baseSalary * 0.10;  // 10% bonus by default
    }

    public void performDuties() {
        System.out.println(name + " is performing general employee duties");
    }

    public final void attendMeeting() {  // Cannot be overridden
        System.out.println(name + " is attending a meeting");
    }
}

class Developer extends Employee {
    private String programmingLanguage;

    public Developer(String name, double baseSalary, String language) {
        super(name, baseSalary);
        this.programmingLanguage = language;
    }

    @Override
    public double calculateBonus() {
        return baseSalary * 0.15;  // 15% bonus for developers
    }

    @Override
    public void performDuties() {
        System.out.println(name + " is writing code in " + programmingLanguage);
    }

    public void debugCode() {
        System.out.println(name + " is debugging code");
    }
}

class SalesPerson extends Employee {
    private double salesAmount;

    public SalesPerson(String name, double baseSalary, double salesAmount) {
        super(name, baseSalary);
        this.salesAmount = salesAmount;
    }

    @Override
    public double calculateBonus() {
        return baseSalary * 0.10 + salesAmount * 0.05;  // 10% + 5% of sales
    }

    @Override
    public void performDuties() {
        System.out.println(name + " is making sales calls");
    }

    public void makeSale(double amount) {
        salesAmount += amount;
        System.out.println("Sale made: $" + amount);
    }
}

super in Method Overriding

java
class Vehicle {
    protected int speed;

    public Vehicle(int speed) {
        this.speed = speed;
    }

    public void displayInfo() {
        System.out.println("Vehicle speed: " + speed + " mph");
    }
}

class Car extends Vehicle {
    private String model;

    public Car(int speed, String model) {
        super(speed);
        this.model = model;
    }

    @Override
    public void displayInfo() {
        super.displayInfo();  // Call parent method
        System.out.println("Car model: " + model);
    }
}

Method Overriding vs Method Overloading

java
class Calculator {
    // Method overloading (compile-time polymorphism)
    public int add(int a, int b) {
        return a + b;
    }

    public double add(double a, double b) {
        return a + b;
    }
}

class ScientificCalculator extends Calculator {
    // Method overriding (runtime polymorphism)
    @Override
    public int add(int a, int b) {
        System.out.println("Using scientific addition");
        return a + b;
    }
}

Best Practices for Method Overriding

  1. Always use @Override annotation
  2. Maintain the method contract (Liskov Substitution Principle)
  3. Don't change the fundamental behavior of the method
  4. Use super to extend rather than replace parent behavior
  5. Document any behavioral changes in the overriding method