Inheritance & Polymorphism

Method Overriding & @Override

15 min Lesson 3 of 14

Method Overriding & @Override

Inheritance lets a child class reuse a parent's code. But what if the child needs to change how an inherited method works? That is exactly what method overriding is for. You write a new implementation of a parent method inside the subclass, and Java calls the child's version whenever the object is a child type.

What Is Method Overriding?

When a subclass provides its own definition of a method that already exists in a superclass, that is called overriding. The method in the subclass must have the same name, the same parameter list, and a compatible return type as the one in the superclass.

class Animal { public String speak() { return "..."; } } class Dog extends Animal { public String speak() { // overrides Animal.speak() return "Woof!"; } }

When you call speak() on a Dog object, Java runs the Dog version, not the Animal version — even if the variable type is Animal. (You will explore this in depth in the Polymorphism lesson.)

The @Override Annotation

Java lets you mark an overriding method with the @Override annotation. This is optional at runtime, but strongly recommended.

class Dog extends Animal { @Override public String speak() { return "Woof!"; } }
Why use @Override? It tells the compiler: "I intend this to override a method in the parent." If you spell the name wrong, change the parameters by accident, or the parent method no longer exists, the compiler reports an error immediately instead of silently creating an unrelated new method. Always write it.

The Four Rules for Overriding

  1. Same method name — must match exactly.
  2. Same parameter list — number, types, and order. Different parameters make a new overloaded method, not an override.
  3. Compatible return type — either the same type or a subtype (covariant return; see below).
  4. Access modifier must not be more restrictive — if the parent declares public, the child cannot declare protected or private. It may stay public or become more open, but never narrower.
Overriding vs. Overloading: These are easy to confuse. Overloading means the same name with different parameters — no inheritance required. Overriding means the same name and same parameters in a subclass. If you add @Override to a method that only differs in parameters, the compiler will catch the mistake.

Covariant Return Types

Since Java 5, an overriding method may return a subtype of the parent's return type. This is called a covariant return type. It is useful when the parent returns a general type and the child can promise something more specific.

class Shape { public Shape copy() { return new Shape(); } } class Circle extends Shape { @Override public Circle copy() { // Circle is a subtype of Shape — allowed return new Circle(); } }

Callers of Circle.copy() get back a Circle without any casting. This improves type safety without breaking compatibility.

Covariant return types shine in builder-style classes and factory methods. Returning a more specific type means callers keep the benefit of the concrete type while the API still fits into the parent hierarchy.

What Cannot Be Overridden

  • private methods — they are invisible to subclasses, so they cannot be overridden (only hidden).
  • static methods — these are resolved at compile time, not runtime. A subclass can declare a static method with the same signature, but that is method hiding, not overriding.
  • final methods — the final keyword explicitly forbids overriding. You will learn about final in a later lesson.

A Complete Working Example

class Employee { private String name; public Employee(String name) { this.name = name; } public String getName() { return name; } public double calculateBonus(double salary) { return salary * 0.05; // 5% default bonus } } class Manager extends Employee { public Manager(String name) { super(name); } @Override public double calculateBonus(double salary) { return salary * 0.15; // managers earn 15% } } class Intern extends Employee { public Intern(String name) { super(name); } @Override public double calculateBonus(double salary) { return 0; // interns receive no bonus } } // Usage Employee e1 = new Manager("Alice"); Employee e2 = new Intern("Bob"); System.out.println(e1.getName() + ": " + e1.calculateBonus(80_000)); // 12000.0 System.out.println(e2.getName() + ": " + e2.calculateBonus(30_000)); // 0.0

Each class defines what a bonus means for its own type. The same method call produces different results depending on the actual object — this is the power of overriding working alongside polymorphism.

Summary

  • Overriding lets a subclass replace a parent method's implementation.
  • Always add @Override — the compiler catches typos and mismatches for you.
  • Follow the four rules: same name, same parameters, compatible (covariant) return type, and equal or wider access.
  • Covariant return types let you return a more specific type without breaking the contract.
  • private, static, and final methods cannot be overridden.