Skip to content
Part IA Michaelmas Term

Method Overriding

What Is Overriding?

A subclass overrides an inherited method by declaring a method with the same signature (name plus parameter types) and a covariant or identical return type. The overriding method replaces the superclass implementation for all instances of the subclass.

class Animal {
    String speak() { return "..."; }
}

class Dog extends Animal {
    @Override
    String speak() { return "Woof"; }
}

Key requirements for a valid override:

  • Same method name
  • Same parameter types (number, order, and types must match exactly)
  • Return type must be the same, or a subtype of the original return type (covariant return)
  • Access modifier must be at least as permissive (cannot narrow visibility)
  • throws clause: the overriding method cannot throw broader checked exceptions than the overridden method

@Override Annotation

The @Override annotation tells the compiler “I intend this method to override a superclass method.” If the signature does not actually match any inherited method, the compiler emits an error.

class Dog extends Animal {
    @Override
    String speek() { ... }  // compile error: no speek() in superclass
}

Without @Override, the misspelled method would silently become a new, unrelated method — no override, no error, just incorrect behaviour. Always use @Override.

Dynamic (Runtime) Dispatch

Method calls are resolved dynamically by the JVM at runtime, using the object’s actual runtime class:

class A {
    void speak() { System.out.println("A"); }
}

class B extends A {
    @Override
    void speak() { System.out.println("B"); }
}

A a = new B();
a.speak();  // prints "B" — dynamic dispatch uses runtime type (B)

Even though a’s declared type is A, the JVM looks up speak() on the object’s actual class (B) and calls B.speak(). This is the foundation of polymorphism.

Access Modifier Rules

An overriding method can make the method more visible but not less:

SuperclassValid overridesInvalid overrides
publicpublic onlyprotected, package-private, private
protectedpublic, protectedpackage-private, private
package-privatepublic, protected, package-privateprivate
privateCannot be overridden at all (private methods are not inherited)N/A

A private method in the superclass is invisible to the subclass. If the subclass declares a method with the same signature, it is a completely separate method — not an override.

final Methods

A method declared final cannot be overridden by any subclass:

class Animal {
    final void vitalFunction() { ... }
}

class Dog extends Animal {
    @Override
    void vitalFunction() { ... }  // compile error
}

Marking a method final communicates that this behaviour is essential to the class’s contract and must not be changed by subclasses. It also allows the JIT compiler to optimise method calls more aggressively.

static Methods Are Hidden, Not Overridden

static methods belong to the class, not instances. If a subclass declares a static method with the same signature, it hides the superclass’s static method — similar to field shadowing:

class A {
    static void m() { System.out.println("A.m"); }
}

class B extends A {
    static void m() { System.out.println("B.m"); }
}

A a = new B();
a.m();  // prints "A.m" — resolved by declared type (like field access)

Static methods use static (compile-time) resolution, not dynamic dispatch. This is another common Tripos trap.

Covariant Return Types

Since Java 5, the overriding method’s return type can be a subtype of the overridden method’s return type:

class Animal {
    Animal reproduce() { return new Animal(); }
}

class Dog extends Animal {
    @Override
    Dog reproduce() { return new Dog(); }  // covariant: Dog is a subtype of Animal
}

This is useful for implementing the prototype pattern or builder-like methods where the caller expects a more specific type.

super.method() — Calling the Overridden Version

Within a subclass, super.methodName(args) calls the superclass’s implementation, bypassing the subclass’s override:

class Dog extends Animal {
    @Override
    void eat() {
        super.eat();  // do the Animal thing
        wagTail();    // then do the Dog thing
    }
}

This lets you extend behaviour rather than completely replace it.

Overriding vs Overloading

OverridingOverloading
WhereSubclass (same hierarchy)Same class or subclass
SignatureSame name, same parameter typesSame name, different parameter types
Return typeSame or covariantCan be different
ResolutionRuntime (dynamic dispatch)Compile time (static resolution)
Annotation@OverrideNo special annotation
Access modifierCannot narrowNo restriction

Overloading a method that is also overridden can create confusing resolution. The compiler picks the overload based on argument types; the JVM picks the overriding implementation based on the runtime object type. Both mechanisms are involved in a single call.