Method Overriding & Overloading

Method Overriding

Method overriding happens if the sub-class method satisfies the below conditions with the Super-class method:

  • Method name should be the same

  • The argument or parameters should be the same

  • Return type should also be the same

  • The method cannot have a weaker access modifier (e.g., protected method in superclass cannot be private in subclass).

  • The method cannot throw new or broader checked exceptions.

  • Only instance methods can be overridden (static methods are hidden, not overridden).

  • Subclass can return a subtype of the superclass method’s return type.

The key benefit of overriding is that the Sub-class can provide some specific information about that sub-class type than the super-class.

Example

// Basic Overriding Example
class Parent {
    void show() { System.out.println("Parent method"); }
}

class Child extends Parent {
    @Override
    void show() { System.out.println("Child method"); }
}

// Overriding with Covariant Return Type
// The overridden method in the subclass can return a subtype of the superclass method’s return type.
class Parent {
    Number getValue() { return 10; }
}

class Child extends Parent {
    @Override
    Integer getValue() { return 20; } // Covariant return type (Integer is a subtype of Number)
}

// Overriding with Exception Handling
// The overridden method cannot throw broader checked exceptions.
class Parent {
    void method() throws IOException {}  
}

class Child extends Parent {
    @Override
    void method() throws FileNotFoundException {} // Allowed (Narrower exception)
    
    // void method() throws Exception {} // Not allowed (Broader exception)
}

// Hiding Static Methods (Not Overriding)
// Static methods are not overridden, they are hidden.
class Parent {
    static void show() { System.out.println("Parent static method"); }
}

class Child extends Parent {
    static void show() { System.out.println("Child static method"); }
}

// Final Methods Cannot Be Overridden
class Parent {
    final void show() { System.out.println("Final method"); }
}

class Child extends Parent {
    // void show() {} // Compilation error (Cannot override final method)
}

// Using super to Call Parent Method
class Parent {
    void show() { System.out.println("Parent method"); }
}

class Child extends Parent {
    @Override
    void show() {
        super.show(); // Calls the parent method
        System.out.println("Child method");
    }
}

// Incorrect use of @Override
class Parent {
    void method(int a) {}
}

class Child extends Parent {
    // @Override void method(double a) {} // Compilation error (Not the same signature)
}

// Private methods cannot be overridden (they are not inherited)
class Parent {
    private void secret() {} 
}

class Child extends Parent {
    void secret() {} // This is a new method, NOT an override
}

// Illegal Case (Incompatible Covariant Type)
class Parent {
    Number getValue() {
        return 10;
    }
}

class Child extends Parent {
    @Override
    String getValue() { // Compilation error: String is NOT a subclass of Number
        return "Error";
    }
}

Method Overloading

Method overloading happens for different classes or within the same class.

For method overloading, sub-class method should satisfy the below conditions with the Super-class method (or) methods in the same class itself:

  • Same method name

  • Different argument types and ordering

  • Return type alone cannot differentiate overloaded methods. Does not support covariant return type.

  • They can have different access modifiers and exceptions.

Example

// Different Number of Parameters
class Calculator {
    int add(int a, int b) { return a + b; }
    int add(int a, int b, int c) { return a + b + c; }
}

// Different Parameter Types
class Display {
    void show(int num) { System.out.println("Int: " + num); }
    void show(double num) { System.out.println("Double: " + num); }
    
    int add(int a, int b) { return a + b; }
    int add(Integer a, int b) { return a + b; }
    int add(int a, Integer b) { return a + b; }
    int add(Integer a, Integer b) { return a + b; }
}

// Different Order of Parameters
class Printer {
    void print(String s, int i) { System.out.println("String first: " + s + ", " + i); }
    void print(int i, String s) { System.out.println("Int first: " + i + ", " + s); }
}

// Overloading with Type Promotion
// That is commenting method display(int a) will still work here without compilation issue
class TypePromotionExample {
    void display(int a) { System.out.println("int: " + a); }
    void display(double a) { System.out.println("double: " + a); }
    
    public static void main(String[] args) {
        TypePromotionExample obj = new TypePromotionExample();
        obj.display(10); // Calls display(int), but if it were absent, it would promote to display(double)
    }
}

// Overloading with Type Promotion
// Commenting display(int a) will make compiler use display(double a) and commenting it as well will
// take void display(Integer a)
class TypePromotionExample {
    void display(int a) { System.out.println("int: " + a); }
    void display(double a) { System.out.println("double: " + a); }
    void display(Integer a) { System.out.println("integer: " + a); }
    
    public static void main(String[] args) {
        TypePromotionExample obj = new TypePromotionExample();
        obj.display(10); // Calls display(int), but if it were absent, it would promote to display(double)
    }
}

// Overloading with Varargs (Variable Arguments)
class VarargsExample {
    void sum(int... numbers) {
        int total = 0;
        for (int num : numbers) total += num;
        System.out.println("Sum: " + total);
    }
}

// Cannot overload just by changing the return type
class Test {
    int method() { return 1; }
    // double method() { return 2.0; } // Compilation error (Return type alone cannot distinguish methods)
}

// Varargs ambiguity when combined with other methods
class Ambiguous {
    void show(int a, int b) {}  
    void show(int... nums) {}  // Ambiguous when called with two int arguments
}

// Invalid Overloading with Covariant Return Type
class Example {
    Number getValue() { return 10; }

    // Compilation error: Overloading is based only on parameters, not return type
    Integer getValue() { return 20; }
}

class Example {
    Number getValue() { return 10; }  // Method 1

    Integer getValue(int x) { return 20; }  // Method 2 (Valid overload because of different parameter)
}

// Overloading with Different Checked Exceptions
class ExceptionOverloading {
    // Method with IOException
    void readFile(String filePath) throws IOException {
        System.out.println("Reading file: " + filePath);
    }

    // Overloaded method with SQLException
    void readFile(int fileId) throws SQLException {
        System.out.println("Fetching file with ID: " + fileId);
    }
}

// Overloading with Different Unchecked Exceptions
// Method with ArithmeticException
class UncheckedExceptionOverloading {
    // Method with ArithmeticException
    void divide(int a, int b) throws ArithmeticException {
        System.out.println("Result: " + (a / b));
    }

    // Overloaded method with NullPointerException
    void divide(String a, String b) throws NullPointerException {
        if (a == null || b == null) {
            throw new NullPointerException("Input cannot be null");
        }
        System.out.println("Concatenated: " + a + b);
    }
}

// Overloading with and without Exception
class MixedExceptionOverloading {
    // Method without exception
    void process(int num) {
        System.out.println("Processing number: " + num);
    }

    // Overloaded method with an exception
    void process(String data) throws NumberFormatException {
        int num = Integer.parseInt(data); // May throw NumberFormatException
        System.out.println("Converted number: " + num);
    }
}

Overloading by Only Return Type ?

In Java, method overloading means having multiple methods in the same class with the same name but different parameter lists (number or type of arguments).

But if two methods have the same name and the same parameter list, but only differ in return type, the compiler cannot distinguish between them at the point of calling.

Example Case

class Test {
    int getValue() {
        return 10;
    }

    double getValue() {   // Compile-time error
        return 20.5;
    }
}

Why This Fails ?

When we call:

Test t = new Test();
t.getValue();
  • The compiler sees the method call getValue() with no arguments.

  • Both methods int getValue() and double getValue() look identical in terms of method signature (method name + parameter list).

  • Java does not consider return type as part of the signature.

  • So, the compiler cannot figure out which version we want to call.

This would cause ambiguity.

What If Return Type Was Considered?

If Java allowed overloading based only on return type, then a call like this:

t.getValue();

would leave the compiler confused. Should it return an int or a double? Unless we explicitly assign it:

int a = t.getValue();
double b = t.getValue();

the compiler has no way to know which method we meant. This would make method resolution complicated and ambiguous.

Last updated