Skip to content

Java Overriding

Method overriding is a fundamental concept in object-oriented programming (OOP) and a key feature of polymorphism in Java. It allows a subclass (child class) to provide a specific implementation for a method that is already defined in its superclass (parent class).

When a subclass overrides a method, it essentially replaces the inherited implementation with its own version. This enables objects of the subclass to exhibit behavior specific to their type, even when referred to by a variable of the superclass type.

  • Specialized Behavior: Allows subclasses to define behavior tailored to their specific needs while adhering to the contract defined by the superclass method.
  • Runtime Polymorphism: Enables the Java Virtual Machine (JVM) to determine which method version (superclass’s or subclass’s) to execute at runtime, based on the actual object type, not the reference variable type.

Consider a Vehicle superclass and a Car subclass.

// Superclass
class Vehicle {
String brand;
public Vehicle(String brand) {
this.brand = brand;
}
public void displayInfo() {
System.out.println("Vehicle Brand: " + brand);
}
public void startEngine() {
System.out.println("Vehicle engine starting...");
}
}
// Subclass
class Car extends Vehicle {
int numberOfDoors;
public Car(String brand, int doors) {
super(brand); // Call the superclass constructor
this.numberOfDoors = doors;
}
// --- Overriding the displayInfo method ---
@Override // Annotation to ensure correct overriding (Recommended!)
public void displayInfo() {
System.out.println("Car Brand: " + brand + ", Doors: " + numberOfDoors);
}
// Car-specific method
public void honk() {
System.out.println("Car honking: Beep beep!");
}
}
public class TestVehicle {
public static void main(String[] args) {
Vehicle genericVehicle = new Vehicle("Generic");
Vehicle myCar = new Car("Toyota", 4); // Vehicle reference, Car object
Car anotherCar = new Car("Honda", 2);
System.out.println("--- Calling displayInfo() ---");
genericVehicle.displayInfo(); // Calls Vehicle's version
myCar.displayInfo(); // Calls Car's overridden version (Runtime Polymorphism)
anotherCar.displayInfo(); // Calls Car's overridden version
System.out.println("\n--- Calling startEngine() --- (Inherited)");
genericVehicle.startEngine();
myCar.startEngine(); // Inherited from Vehicle
anotherCar.startEngine();
System.out.println("\n--- Calling car-specific method --- ");
// myCar.honk(); // Compile Error! 'myCar' is Vehicle type, doesn't know 'honk()'
anotherCar.honk(); // Works fine, 'anotherCar' is Car type
// To call honk() on myCar, you need to cast:
if (myCar instanceof Car) { // Check type before casting
((Car) myCar).honk();
}
}
}

Output:

--- Calling displayInfo() ---
Vehicle Brand: Generic
Car Brand: Toyota, Doors: 4
Car Brand: Honda, Doors: 2
--- Calling startEngine() --- (Inherited)
Vehicle engine starting...
Vehicle engine starting...
Vehicle engine starting...
--- Calling car-specific method ---
Car honking: Beep beep!
Car honking: Beep beep!
  • The Car class extends Vehicle, inheriting its fields and methods.
  • Car provides its own displayInfo method, marked with @Override. This version prints car-specific details.
  • When myCar.displayInfo() is called, even though myCar is declared as type Vehicle, the JVM determines at runtime that the actual object is a Car. Therefore, it executes the overridden displayInfo method from the Car class.
  • startEngine() is not overridden, so both genericVehicle and myCar use the implementation inherited from Vehicle.
  • The honk() method exists only in Car. You cannot call it directly on a Vehicle reference (myCar) without casting, because the compiler only knows about methods defined in the Vehicle class (or its superclasses).

It is highly recommended to use the @Override annotation whenever you intend to override a superclass method.

  • Compile-Time Check: The compiler verifies that the annotated method actually overrides a method from a superclass or implements a method from an interface. If you make a mistake (e.g., misspell the method name, use incorrect parameters), you’ll get a compile-time error instead of a subtle runtime bug.
  • Readability: Clearly indicates the programmer’s intent to override, making the code easier to understand.

To correctly override a method, the following rules must be followed:

  • Method Signature: The overriding method in the subclass must have the exact same name and parameter list (number, type, and order of parameters) as the method in the superclass.
  • Return Type: The return type must be the same as, or a subtype of (covariant return type), the return type declared in the superclass method. (e.g., If superclass returns Vehicle, subclass can return Car).
  • Access Modifier: The access level of the overriding method cannot be more restrictive than the overridden method’s access level. (e.g., If superclass method is public, subclass method cannot be protected or private. If superclass is protected, subclass can be protected or public).
  • final Methods: Methods declared final in the superclass cannot be overridden.
  • static Methods: Methods declared static cannot be overridden (they belong to the class, not instances). If a subclass declares a static method with the same signature, it’s called method hiding, not overriding.
  • private Methods: Methods declared private are not inherited and thus cannot be overridden.
  • Constructors: Constructors are not inherited and cannot be overridden.
  • Exceptions: The overriding method can throw:
    • Any unchecked exceptions (subclasses of RuntimeException or Error), regardless of what the overridden method throws.
    • The same checked exceptions (subclasses of Exception but not RuntimeException) as the overridden method.
    • Subclasses of the checked exceptions thrown by the overridden method.
    • Fewer checked exceptions than the overridden method, or no checked exceptions at all.
    • It cannot throw new or broader checked exceptions than those declared by the overridden method.

Sometimes, within an overriding method, you might want to execute the original implementation from the superclass before adding the subclass-specific behavior. You can achieve this using the super keyword.

super.methodName() calls the superclass’s version of the method.

class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
super.makeSound(); // Call the Animal's makeSound() method first
System.out.println("Dog barks: Woof woof!"); // Add Dog-specific behavior
}
}
public class TestSuper {
public static void main(String[] args) {
Animal myDog = new Dog();
myDog.makeSound();
}
}

Output:

Animal makes a sound
Dog barks: Woof woof!

Don’t confuse overriding with overloading:

FeatureMethod OverridingMethod Overloading
PurposeProvide specific implementation in subclass for an inherited method.Provide multiple methods with the same name but different parameters within the same class.
Method SignatureSame name, same parameter list.Same name, different parameter list (number, type, or order).
RelationshipOccurs between superclass and subclass (inheritance).Occurs within the same class.
Return TypeCan be same or covariant.Can be different (return type alone doesn’t distinguish).
PolymorphismRuntime polymorphism (dynamic dispatch).Compile-time polymorphism (static binding).
ExampleCar.displayInfo() overrides Vehicle.displayInfo().Calculator.add(int, int) and Calculator.add(double, double).

Method overriding is a cornerstone of polymorphism. The next chapter, ‘Java Polymorphism’, will explore this concept in more detail, showing how overriding enables flexible and extensible object-oriented designs.