Method Overriding

IB Syllabus: B3.2 (HL) – Override methods in subclasses to provide specialised behaviour.

HL Only – This page covers content assessed at HL level only.

Table of Contents

  1. Key Concepts
    1. What is Method Overriding?
    2. The @Override Annotation
    3. Overriding Rules
    4. Overriding vs Overloading
    5. Calling the Parent’s Version with super
    6. toString() Override
  2. Worked Examples
    1. Example 1: Shape Hierarchy
  3. Quick Check
  4. Spot the Error
  5. Predict the Output
  6. Practice Exercises
    1. Core
    2. Extension
    3. Challenge
  7. Connections

Key Concepts

What is Method Overriding?

When a child class defines a method with the same name, same parameters, and same return type as a method in its parent class, the child’s version replaces the parent’s version for objects of the child type. This is method overriding.

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

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

public class Cat extends Animal {
    @Override
    public String speak() {
        return "Meow!";
    }
}
Dog d = new Dog();
Cat c = new Cat();
Animal a = new Animal();

System.out.println(d.speak());  // Woof!
System.out.println(c.speak());  // Meow!
System.out.println(a.speak());  // ...

Each subclass provides its own version of speak(). When you call speak() on a Dog, Java uses the Dog’s version, not the Animal’s.

The @Override Annotation

@Override is an annotation placed above a method to tell the compiler “I intend to override a parent method.” It is optional but strongly recommended because:

  1. If you misspell the method name, the compiler catches it immediately
  2. If you accidentally change the parameter types, the compiler warns you
  3. It makes the code easier to read – anyone reading the class knows this method replaces a parent’s version
@Override
public String speak() {   // compiler checks: does Animal have speak()?
    return "Woof!";
}

Without @Override, a typo like speek() would create a NEW method instead of overriding the parent’s speak(). The code would compile but behave incorrectly – a subtle bug.

Always use @Override when overriding a method. It costs nothing and prevents hard-to-find bugs. IB mark schemes expect to see it.

Overriding Rules

For a method to override a parent method, it must have:

  1. Same method name as the parent
  2. Same parameter list (number, types, and order)
  3. Same return type (or a subtype)
  4. Same or less restrictive access modifier (e.g., a protected parent method can be overridden as public, but not as private)

If any of these differ, it is not an override – it is a new method (overloading or a completely separate method).

Overriding vs Overloading

These are often confused but are fundamentally different:

  Overriding Overloading
Where Child class redefines a parent method Same class defines multiple methods with the same name
Parameters Must be identical to the parent Must be different (different number or types)
Return type Must be the same (or subtype) Can be different
Which runs? Decided at runtime (based on actual object type) Decided at compile time (based on parameter types)
Purpose Specialise behaviour for a subtype Provide multiple ways to call the same operation
// OVERLOADING -- same class, different parameters
public class Calculator {
    public int add(int a, int b) { return a + b; }
    public double add(double a, double b) { return a + b; }
    public int add(int a, int b, int c) { return a + b + c; }
}

// OVERRIDING -- child class, same method signature
public class Animal {
    public String speak() { return "..."; }
}
public class Dog extends Animal {
    @Override
    public String speak() { return "Woof!"; }  // same name, same params
}

Calling the Parent’s Version with super

Sometimes you want to extend the parent’s behaviour rather than completely replace it. Use super.methodName() to call the parent’s version from inside the override:

public class Employee {
    private String name;
    private double baseSalary;

    public Employee(String name, double baseSalary) {
        this.name = name;
        this.baseSalary = baseSalary;
    }

    public double calculatePay() {
        return baseSalary;
    }

    public String toString() {
        return name + ": $" + calculatePay();
    }
}

public class Manager extends Employee {
    private double bonus;

    public Manager(String name, double baseSalary, double bonus) {
        super(name, baseSalary);
        this.bonus = bonus;
    }

    @Override
    public double calculatePay() {
        return super.calculatePay() + bonus;  // parent's pay + bonus
    }
}
Employee e = new Employee("Alice", 50000);
Manager m = new Manager("Bob", 60000, 10000);

System.out.println(e.calculatePay());  // 50000.0
System.out.println(m.calculatePay());  // 70000.0 (60000 + 10000)

The Manager’s calculatePay() reuses the parent’s calculation via super.calculatePay() and adds the bonus on top. This avoids duplicating the base salary logic.

toString() Override

Every Java class inherits a toString() method from Object (the root of all Java classes). By default, it returns something unhelpful like Student@1a2b3c. Overriding it lets you define a meaningful string representation:

public class Student {
    private String name;
    private int grade;

    public Student(String name, int grade) {
        this.name = name;
        this.grade = grade;
    }

    @Override
    public String toString() {
        return name + " (Grade " + grade + ")";
    }
}
Student s = new Student("Alice", 11);
System.out.println(s);  // Alice (Grade 11)
// Without override: Student@7a81197d

System.out.println() automatically calls toString() on objects. Overriding it makes debugging and output much easier.


Worked Examples

Example 1: Shape Hierarchy

public class Shape {
    public double getArea() {
        return 0;
    }

    public String describe() {
        return "Shape with area " + getArea();
    }
}

public class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

public class Rectangle extends Shape {
    private double width, height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double getArea() {
        return width * height;
    }
}
Circle c = new Circle(5);
Rectangle r = new Rectangle(4, 6);

System.out.println(c.describe());  // Shape with area 78.53981633974483
System.out.println(r.describe());  // Shape with area 24.0

Notice: describe() is NOT overridden – it is inherited from Shape. But it calls getArea(), which IS overridden. So when c.describe() runs, it calls the Circle’s getArea(), not the Shape’s. This is the power of overriding.


Quick Check

Q1. What is method overriding?

Q2. Why should you use @Override?

Q3. A parent has void print(String s). A child defines void print(int n). Is this overriding?

Q4. What does super.calculatePay() do inside an overridden method?


Spot the Error

A student wants Dog to override Animal's speak() method, but calling d.speak() still returns "..." (the parent's version). Click the buggy line, then pick the fix.

1public class Dog extends Animal { 2 @Override 3 public String speak(String sound) { 4 return "Woof!"; 5 } 6}

Pick the fix:


Predict the Output

Using the Employee/Manager classes from the Key Concepts section:

Manager m = new Manager("Bob", 60000, 10000);
System.out.println(m.calculatePay());

Using the Shape/Circle classes from Example 1, what does c.describe() start with?

Circle c = new Circle(5);
System.out.println(c.describe());

Does the output start with "Shape" or "Circle"?


Practice Exercises

Core

  1. Override toString() – Create a Book class with title and author. Override toString() to return "Title by Author". Create a Book and print it directly with System.out.println(book).

  2. Identify override vs overload – For each pair, state whether the child method overrides or overloads the parent:

    • (a) Parent: void draw(), Child: void draw()
    • (b) Parent: int calculate(int x), Child: int calculate(int x, int y)
    • (c) Parent: String getName(), Child: String getName()
    • (d) Parent: void print(String s), Child: void print(int n)

Extension

  1. Extend with super – Create a Vehicle class with getDescription() returning "Vehicle: [make]". Create a Car subclass that overrides getDescription() to return the parent’s description plus " (4 doors)". Use super.getDescription().

  2. The describe() trick – In Example 1, Shape.describe() calls getArea(). Explain why c.describe() (where c is a Circle) uses Circle’s getArea() even though describe() is defined in Shape. What OOP principle makes this work?

Challenge

  1. Payment system – Create a class hierarchy: Employee (base salary), Manager (base + bonus), SalesPerson (base + commission percentage * sales total). All override calculatePay(). Create an array of Employees, fill it with a mix of types, and calculate the total payroll.

Connections

  • Prerequisites: Inheritance – must understand extends and super before overriding
  • Next: Polymorphism – overriding enables polymorphic behaviour
  • Related: Constructors – constructor overloading vs method overriding

© EduCS.me — A resource hub for IB Computer Science

This site uses Just the Docs, a documentation theme for Jekyll.