Method Overriding in Java: A Complete Guide

Introduction

Method overriding is a core feature of Java’s object-oriented programming model that allows a subclass to provide a specific implementation of a method already defined in its superclass. This mechanism enables runtime polymorphism, where the version of the method that gets executed is determined by the actual object type at runtime, not the reference type at compile time. Method overriding supports the principle of inheritance while allowing specialized behavior in derived classes, making code more flexible, extensible, and reusable.


1. What Is Method Overriding?

Method overriding occurs when a subclass defines a method with the same signature (name, parameter list, and return type) as a method in its superclass. The subclass method replaces the inherited method for instances of the subclass.

Key Requirements for Overriding

  1. Same method name
  2. Same parameter list (number, type, and order)
  3. Same or covariant return type (since Java 5)
  4. Access modifier must not be more restrictive than the superclass method
  5. Must not throw new or broader checked exceptions than the superclass method

Note: The methods must be in a parent-child (inheritance) relationship.


2. Basic Example

// Superclass
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
// Subclass
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks: Woof!");
}
}
// Usage
public class Main {
public static void main(String[] args) {
Animal myPet = new Dog(); // Polymorphic reference
myPet.makeSound(); // Output: Dog barks: Woof!
}
}

Output: Dog barks: Woof!
Even though the reference type is Animal, the actual object is Dog, so the overridden method is called.


3. Rules of Method Overriding

A. Return Type

  • Must be the same as the superclass method, or
  • A covariant return type (a subclass of the original return type).
class Shape {
public Shape clone() { return new Shape(); }
}
class Circle extends Shape {
@Override
public Circle clone() { return new Circle(); } // Covariant return
}

B. Access Modifiers

The subclass method can have the same or less restrictive access:

  • protectedpublic
  • publicprotected ❌ (more restrictive)

C. Exception Handling

  • Can throw narrower or fewer checked exceptions.
  • Can throw any unchecked (runtime) exceptions.
  • Cannot throw new or broader checked exceptions.
class Parent {
public void read() throws IOException { }
}
class Child extends Parent {
@Override
public void read() throws FileNotFoundException { } // ✅ (FileNotFoundException ⊂ IOException)
// public void read() throws Exception { } // ❌ Broader exception
}

D. Final and Static Methods

  • final methods cannot be overridden (by design).
  • static methods cannot be overridden—they are hidden (method hiding, not overriding).

4. The @Override Annotation

Although optional, using @Override is a best practice:

  • Helps the compiler verify that overriding is valid.
  • Catches errors if the superclass method signature changes.
class Bird extends Animal {
@Override
public void makeSound() {
System.out.println("Bird chirps");
}
}

If makeSound() didn’t exist in Animal, the compiler would flag an error.


5. Runtime Polymorphism (Dynamic Method Dispatch)

Method overriding enables dynamic method dispatch: the JVM decides which method to call based on the actual object type, not the reference type.

Animal a1 = new Animal();
Animal a2 = new Dog();
Animal a3 = new Bird();
a1.makeSound(); // Animal makes a sound
a2.makeSound(); // Dog barks: Woof!
a3.makeSound(); // Bird chirps

This is the foundation of polymorphic behavior in Java.


6. Overriding vs. Overloading

FeatureMethod OverridingMethod Overloading
InheritanceRequired (subclass vs. superclass)Not required (same class)
SignatureMust be identicalMust differ
Return TypeSame or covariantCan differ
BindingRuntime (dynamic)Compile-time (static)
PurposeSpecialize behaviorProvide multiple ways to call a method

7. Common Use Cases

A. Customizing Behavior in Subclasses

class Vehicle {
public void start() { System.out.println("Vehicle started"); }
}
class ElectricCar extends Vehicle {
@Override
public void start() { System.out.println("Electric car silently started"); }
}

B. Implementing Abstract Methods

abstract class Shape {
abstract double area();
}
class Rectangle extends Shape {
private double width, height;
public Rectangle(double w, double h) { width = w; height = h; }
@Override
public double area() { return width * height; }
}

C. toString(), equals(), hashCode()

All Java objects inherit these from Object; overriding them is essential for meaningful string representation and equality checks.

@Override
public String toString() {
return "Student{name='" + name + "', id=" + id + "}";
}

8. Best Practices

  • Always use @Override to prevent accidental overloading.
  • Preserve the contract of the superclass method (Liskov Substitution Principle).
  • Do not change method semantics—the subclass should be substitutable for the superclass.
  • Call super.method() if you need to extend (not replace) the parent behavior:
  @Override
public void save() {
super.save(); // Perform parent logic
log("Data saved"); // Add subclass-specific behavior
}
  • Avoid overriding non-abstract methods unnecessarily—prefer composition when behavior doesn’t truly specialize.

9. Common Mistakes

  • Changing parameter types → results in overloading, not overriding.
  • Making access more restrictive → compilation error.
  • Overriding static methods → leads to method hiding (not polymorphism).
  • Forgetting @Override → hard-to-detect bugs if superclass changes.

Conclusion

Method overriding is a cornerstone of polymorphism and inheritance in Java. It empowers subclasses to tailor inherited behavior while maintaining a consistent interface, enabling flexible and scalable designs. When combined with abstract classes and interfaces, overriding forms the backbone of Java’s extensibility model. By adhering to its rules and best practices—especially using @Override, respecting access levels, and preserving method contracts—developers can build robust, maintainable, and truly object-oriented systems. Mastery of method overriding is essential for anyone seeking to leverage the full power of Java’s polymorphic capabilities.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper