OOP

20. Method Overriding

Overriding lets a subclass provide a specific implementation of a method declared in a superclass; the selection happens at runtime (dynamic dispatch), not compile time. @Override is mandatory best practice.

Java 21 (core language)

Explanation

When you override a method, the JVM uses the actual runtime type of the object to select which implementation to call โ€” not the declared type of the reference. This is the core mechanism of polymorphism.

Rules for a valid override: the method signature (name + parameter types) must match exactly. The return type must be the same or a covariant (subtype) return. Access cannot be made more restrictive (you can widen from protected to public, not narrow from public to protected). The overriding method cannot declare new checked exceptions not declared in the parent.

Always use @Override โ€” it causes a compile error if the method does not actually override anything (typo protection). final methods cannot be overridden. static methods are not overridden; they are hidden (resolved at compile time based on reference type).

Example

public class MethodOverriding {
    static class Shape {
        public double area() { return 0.0; }

        // Covariant return: overrider can return a subtype
        public Shape copy() { return new Shape(); }

        // final: cannot be overridden
        public final String type() { return "Shape"; }
    }

    static class Circle extends Shape {
        private final double radius;
        Circle(double radius) { this.radius = radius; }

        @Override // compile error if signature doesn't match โ€” safety net!
        public double area() { return Math.PI * radius * radius; }

        @Override
        public Circle copy() { return new Circle(radius); } // covariant return OK
    }

    static class Square extends Shape {
        private final double side;
        Square(double side) { this.side = side; }

        @Override
        public double area() { return side * side; }
    }

    public static void main(String[] args) {
        Shape[] shapes = { new Circle(5), new Square(4) };

        for (Shape s : shapes) {
            // Runtime dispatch: Circle.area() or Square.area() selected at runtime
            System.out.printf("%s area = %.2f%n", s.getClass().getSimpleName(), s.area());
        }

        // Static methods are hidden, not overridden
        Shape ref = new Circle(1);
        System.out.println(ref.type()); // "Shape" (final โ€” cannot override)

        // Covariant return: copy() on Circle reference returns Circle
        Circle c = new Circle(3);
        Circle copy = c.copy(); // no cast needed โ€” covariant return
        System.out.printf("Copy area: %.2f%n", copy.area());
    }
}

Sources