Overload resolution chooses a method signature using compile-time argument types; overriding chooses the implementation of an already selected instance method at runtime.
Java method overloads: compile-time selection and dispatch
Java 8+. The program uses only JDK classes and runs without a framework.
The variable type is part of the call
A formatter has separate entry points for a general object and a String label. Calling it with a variable declared Object selects the Object overload even if the value stored there happens to be a String. Changing a runtime value does not rerun overload resolution.
The program also passes a SpecializedReceipt through a Receipt reference. The signature is selected first, then the overridden render implementation runs on the actual receipt. That is the separate runtime decision. Keeping both decisions visible helps explain why adding an overload can change a recompiled caller without changing an existing override.
Prefer distinctly named methods when two overloads express different policies. formatText and formatReceipt may be clearer than a family that accidentally interprets null, a boxed number and a collection differently. The compiler can find a legal signature without proving that its semantics match the caller’s intent.
Null and numeric conversions need care
An uncast null can match several reference overloads. If no single most-specific method exists, the call is ambiguous and does not compile. A cast can express the chosen signature, but it does not make dereferencing null safe. This example avoids a crash by making null handling part of each formatting method.
Primitive widening, boxing and variable-arity invocation participate in different applicability phases. Do not memorize one rule such as "the nearest-looking type wins." Test the exact declarations and argument types. Read boxing and variable-arity calls before designing overloaded numeric APIs.
Working program
public class ReceiptDispatch {
static String format(Object value) { return "object"; }
static String format(String value) { return "text"; }
static class Receipt {
String render() { return "standard"; }
}
static class SpecializedReceipt extends Receipt {
@Override String render() { return "priority"; }
}
public static void main(String[] args) {
Object storedLabel = "paid";
System.out.println(format(storedLabel));
System.out.println(format((String) storedLabel));
Receipt receipt = new SpecializedReceipt();
System.out.println(receipt.render());
System.out.println(format((String) null));
}
}Output
object
text
priority
textCost and failure boundaries
Overload selection is a compilation decision, so no runtime scan of every overload occurs in this program. The methods return fixed strings and use O(1) additional storage. Real formatting costs depend on the selected implementation and result size; dispatch does not erase those costs.
A narrowing cast can fail before the method is invoked. Replacing storedLabel with an Integer would make the String cast throw ClassCastException. Tests for an overloaded API should include declared supertype variables, null expressions, primitive values, boxed values and varargs calls, not only directly typed literals.
Common Mistakes
- Do not confuse overload selection with virtual method dispatch.
- Do not use a cast to hide an uncertain value type.
- Do not add unrelated reference overloads without reviewing null calls.
Connect the contracts
Compare the boundary explained in Overriding and runtime behaviour with the assumptions made by this program.
