Topic 3.4
Method Overloading
In one line
Overloading means giving several methods the same name but different parameter lists. The compiler picks which one to call from the number and types of the arguments, at compile time, following a fixed set of rules.
Think of it like this
The word "open". You open a door, open a jar and open a laptop. It's one word, but how you do it depends on what you're opening. Nobody needs three different words because the thing being opened tells you which action is meant. Overloaded methods work the same way: one name, and the arguments tell Java which version to run.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Overloading
- Declaring several methods with the same name but different parameter lists in the same class.
- Overload resolution
- The compiler's process for choosing which overloaded method a call refers to.
- Static type
- The type a variable is declared with, which the compiler knows. In
Object o = "hi";the static type ofoisObject. - Most specific
- Among matching methods, the one whose parameter types are narrower.
f(String)is more specific thanf(Object), because every String is an Object. - Boxing
- Automatically wrapping a primitive in its wrapper object, such as
int5 into anInteger. Unboxing is the reverse. - Ambiguous call
- A call that matches two methods equally well, so the compiler refuses to guess.
- Overriding
- A subclass replacing an inherited method with the same signature. Chosen at run time, unlike overloading. Covered in Topic 5.3.
Step by step
01One name, several parameter lists
Without overloading you'd need areaOfSquare, areaOfRectangle, areaOfCircle. With it, the shape of the arguments says it all.
Each overload is a completely separate method with its own body. They just share a name. The compiler records which one each call means, so at run time there's no searching at all.
static int area(int side) { return side * side; }
static int area(int width, int height) { return width * height; }
static double area(double radius) { return Math.PI * radius * radius; }
area(4); // 16 -> area(int)
area(3, 5); // 15 -> area(int, int)
area(1.0); // 3.14... -> area(double)02What can and can't make an overload
Allowed: a different number of parameters, different types, or a different order of types (f(int, String) vs f(String, int)). The return types and modifiers may differ freely as long as the parameter lists differ.
Not allowed: differing only in return type, parameter names, or static. The compiler compares the signature: name plus parameter types. If two signatures are equal, it reports method area(int) is already defined in class Main.
Why does the return type not count? Because a call doesn't have to use the result: area(3); on its own line gives the compiler nothing to choose by.
03Phase 1: exact match or widening
The compiler first looks for methods that fit without boxing and without varargs. A byte, short, char or int argument may widen to long, float or double; any object widens to its supertypes, ending at Object.
So with f(long) and f(Integer) available, f(5) calls f(long). Widening int to long is allowed in phase 1, while boxing to Integer has to wait for phase 2. Java chose this order in Java 5 so that adding boxing didn't change which method old code called.
04Phases 2 and 3: boxing, then varargs
Only if phase 1 finds nothing does the compiler allow boxing and unboxing (int to Integer or Object, Integer to int). Only if that also fails does it consider varargs methods (f(int...)).
This explains surprises like f(3.0) calling f(Object) when the other options are f(long), f(Integer) and f(int...): a double can't widen to long, so phase 1 fails; in phase 2 it boxes to Double, which is an Object.
05Most specific wins, null included
When several methods match in the same phase, the compiler picks the one with the narrowest parameter types. Between describe(String) and describe(Object), String is more specific.
That's why describe(null) calls describe(String): null fits both, and String is more specific. Add a third overload describe(Integer) and describe(null) becomes ambiguous, because neither String nor Integer is more specific than the other.
06Chosen at compile time, from declared types
Overload resolution looks only at what the compiler knows: the declared types of the arguments. Object o = "hi"; describe(o); calls describe(Object), because to the compiler o is just an Object.
This is called static dispatch (or early binding). Overriding, in Phase 5, is dynamic dispatch: the JVM looks at the real object at run time. Mixing the two up is a classic interview trap.
07Overloading and constructors
Constructors are overloaded all the time: new StringBuilder(), new StringBuilder("abc") and new StringBuilder(64) are three overloads. Topic 4.3 shows how one constructor can call another with this(...).
Overloading is also Java's answer to default arguments: connect(host) can simply call connect(host, 443), so callers who don't care about the port don't have to pass one.
Try it yourself
- 1
Remove an overload and predict the new winner
In "The three phases", delete
f(long). Before running, predict whatf(5),f(b)andf(c)print now. (Phase 1 now finds nothing forint, so phase 2 boxes:f(5)printsf(Integer);byteboxes toByteandchartoCharacter, which match onlyf(Object).) - 2
Make null ambiguous
In "Static types decide", add
static String describe(Integer i). Compile and read the error fordescribe(null). Fix it with a cast:describe((String) null). - 3
Count println's overloads
Run
javap java.io.PrintStream | grep println(orfindstr printlnon Windows). You'll see one line per overload:boolean,char,int,long,float,double,char[],String,Object, and the no-argument version.terminal$ javap java.io.PrintStream | grep println── expected output ──public void println();public void println(boolean);public void println(char);public void println(int);public void println(long);public void println(float);public void println(double);public void println(char[]);public void println(java.lang.String);public void println(java.lang.Object);
Code & diagrams
Expected output
square: 16
rectangle: 15
circle: 3.141592653589793
hectal.in:443
localhost:8080Expected output
f(long)
f(long)
f(long)
f(Integer)
f(Object)
f(Object)
f(int...) with 0 args
f(int...) with 2 argsExpected output
describe(String)
describe(Object)
describe(Object)
describe(String)Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Overload by return type only
Declare int area(int side) and double area(int side) in the same class.
static int area(int side) { return side * side; }
static double area(int side) { return side * side; }Break #2
Write an ambiguous pair
Declare sum(int a, long b) and sum(long a, int b), then call sum(1, 2).
static int sum(int a, long b) { return 1; }
static int sum(long a, int b) { return 2; }
public static void main(String[] args) {
System.out.println(sum(1, 2));
}Myth vs fact
Myth
Overloads can differ by return type.
Fact
Only the parameter list counts. Two methods with the same name and parameter types clash, whatever they return.
Myth
Java picks the overload based on the actual object at run time.
Fact
Overloads are chosen at compile time from declared types. Run-time choice happens only for overriding.
Myth
An int argument always prefers an Integer parameter over a long one.
Fact
Widening (phase 1) is tried before boxing (phase 2), so f(long) wins over f(Integer) for an int.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
JLS 15.12.2 defines the three phases: strict invocation (identity and widening), loose invocation (adds boxing and unboxing), then variable-arity invocation. The phase order was chosen in Java 5 so that adding boxing and varargs didn't change which overload existing code called.
- ▸
In the class file, overloads are distinct methods identified by name plus descriptor, e.g.
area:(I)Iandarea:(D)D. The call site'sinvokestaticnames the exact descriptor, so overload choice costs nothing at run time. The JVM itself even allows two methods differing only in return type (the descriptor includes it), which javac uses for bridge methods but never lets you write. - ▸
Overloads with overlapping parameter types (especially
Objectvs a specific type, or primitive vs wrapper) make APIs fragile:List.remove(int index)vsList.remove(Object o)is the famous example, wherelist.remove(1)removes the element at index 1, not the value 1 (Topic 9.2). Effective Java advises never exporting two overloads with the same number of parameters when the arguments could be confused.
Remember this
- 1
Two methods in a class may share a name if their parameter lists differ: in the number of parameters, their types, or their order.
area(int side),area(int width, int height)andarea(double radius)can all live together. - 2
The return type alone can't distinguish overloads, and neither can parameter names.
int area(int s)anddouble area(int s)clash withmethod area(int) is already defined, because the callarea(3)wouldn't say which one you mean. - 3
The choice is made by the compiler, from the declared (static) types of the arguments. If
Object o = "hi";thendescribe(o)callsdescribe(Object), even though the object is really aString. This is the key difference from overriding (Topic 5.3), which is chosen at run time. - 4
When no overload matches exactly, the compiler tries conversions in three phases: first only widening (like
inttolong, orStringtoObject); then boxing/unboxing (inttoInteger); and only last varargs (Topic 3.5). The first phase that finds any match wins, and within it the most specific method is chosen. - 5
If two candidates are equally specific, the call is ambiguous and won't compile. You fix it by casting an argument or renaming a method.
- 6
You use overloading every day:
System.out.printlnhas ten overloads (forint,double,char,String,Objectand more), andMath.maxhas versions forint,long,floatanddouble.
Explain it without notes
What exactly must differ between two overloaded methods? What doesn't count?
Given f(long), f(Integer) and f(int...), which one does f(5) call, and why?
Why does describe(o) call describe(Object) when o holds a String?
What is the difference between overloading and overriding?
Practice
Write overloads max(int, int), max(int, int, int) and max(double, double), and print max(3, 9), max(4, 1, 7) and max(2.5, 1.5).
Write printAll(int), printAll(String) and printAll(int[]) that print a label and the value(s). Call each once.
Predict the output, then check: overloads g(double) and g(Object); calls g(1), g(1L), g('x') and g(Integer.valueOf(1)).
Trade-offs
- ↔
Overloading gives callers a natural, short API (
max,println), but overloads with the same arity and related types confuse readers and can pick an unexpected method. Prefer distinct names when meanings differ (removeAtvsremoveValue). - ↔
Overloads as default arguments keep calls short, but each extra overload is more surface to document and test. Past two or three, a builder or a parameter object scales better.
Done when you can
Done when you can write a set of overloads and say which one each call picks.
Done when you can explain why return type alone can't overload.
Done when you can apply the three phases: widening, then boxing, then varargs.
Done when you can explain why the declared type, not the object, decides the overload.
Done when you can diagnose and fix an ambiguous call.