Difficulty: Beginner
Differentiate between method overloading and method overriding. What are the rules for each?
This comparison is asked so frequently that you should be able to answer it in your sleep, but the winning move is to go beyond a two-column table and demonstrate that you understand the rules and the corner cases.
Start with the big picture. Overloading is about having many methods with the same name in the same scope but different parameter lists. It is a convenience for the caller: instead of printInt, printDouble, and printString, you have one name print. Overriding is about a subclass replacing the implementation of an inherited method, keeping the same signature. It is what lets a parent reference behave according to the child object.
The rules for overloading are these. The methods must differ in the number, type, or order of parameters. Changing only the return type is not enough and causes a compile error, because the compiler chooses the method from the call arguments and would not know which to pick. Access modifiers and thrown exceptions can be anything, since they are not part of the signature. Overloading is resolved at compile time using the static types of the arguments, so it is called static or early binding. It can happen within a single class or between a parent and child class, and static methods can be overloaded too.
The rules for overriding are stricter. It needs inheritance, so it happens across classes. The method name and parameter list must be identical. The return type must be the same or a subtype of the parent's return type, which is called a covariant return. The access level cannot be more restrictive: a public parent method cannot be overridden as protected. The overriding method cannot throw broader checked exceptions than the parent method. Final, static, and private methods cannot be overridden; a static method with the same signature in the child hides the parent's instead, and a private one is simply an unrelated method. Overriding is resolved at run time based on the actual object, so it is dynamic or late binding. Use the @Override annotation always, so the compiler catches typos like speek instead of speak.
The overload resolution order is a favourite of output questions. Java first looks for an exact or widening match, then tries boxing or unboxing, and finally varargs. When multiple overloads apply, the most specific one wins, which is why passing null to a method overloaded with Object and String picks the String version. In the code example, print(null) prints String, and passing a String stored in an Object variable prints Object, because overloading uses the reference type, not the runtime type.
A memory trick: overloading is about the same name with different parameters, decided by the compiler looking at the arguments; overriding is about the same name and same parameters with different bodies, decided by the JVM looking at the object.
class Printer {
void print(Object o) { System.out.println("Object"); }
void print(String s) { System.out.println("String"); }
}
public class OverloadResolution {
public static void main(String[] args) {
Printer p = new Printer();
p.print("hi");
p.print(10);
Object o = "hi";
p.print(o);
p.print(null);
}
}
print(10) boxes to Integer and matches Object. print(o) uses the declared type Object. null matches both, and String is more specific.
class A {
Number get() { return 1; }
}
class B extends A {
@Override Integer get() { return 2; } // Integer is a subtype of Number
}
public class OverrideDemo {
public static void main(String[] args) {
A ref = new B();
System.out.println(ref.get());
}
}
overloading, overriding, covariant-return, signature