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
- Same method name and parameters as parent class
- Return type should be the same or covariant (subtype)
- Access level cannot be more restrictive
- Cannot override
final
,private
, orstatic
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
- Always use
@Override
annotation - Maintain the method contract (Liskov Substitution Principle)
- Don't change the fundamental behavior of the method
- Use
super
to extend rather than replace parent behavior - Document any behavioral changes in the overriding method