Methods: Parameters, Return Values, and Overloading
How Java methods pass data in and out, and how the compiler picks the right method when several share a name but differ in parameters.
What you'll learn
- Design a method's signature (parameters and return type) to match a stated requirement
- Explain that Java is always pass-by-value, even for reference types
- Predict which overload the compiler selects for a given call
Prerequisites
Explanation
A Java method's signature is its name plus the number, order, and types of its parameters (the return type is not part of the signature for overload-resolution purposes, though it must still be declared). Java is always pass-by-value — this surprises people who expect object arguments to be "passed by reference" because they can mutate the object's fields through the parameter. What's actually happening: for a reference-type parameter, the value being copied is the reference itself, not the object. The method gets its own copy of that reference, pointing at the same object — so mutating fields through it is visible to the caller, but reassigning the parameter to point at a different object inside the method has no effect on the caller's variable, because that reassignment only changes the method's local copy of the reference.
Method overloading lets several methods share one name as long as their parameter lists differ (in count, order, or types) — the compiler picks the correct one at compile time based on the arguments at each call site. print(String s) and print(int n) can coexist; print("hi") calls the first, print(5) calls the second. Overload resolution has a real, sometimes-surprising set of rules: Java prefers an exact type match first, then a match reachable by a widening primitive conversion (an int argument can match a long parameter if no int overload exists), and only as a last resort does it consider autoboxing (int to Integer) or varargs. This means adding a new overload to existing code can occasionally change which overload an existing call resolves to — a real, if rare, source of surprising behavior when a codebase's overload set changes.
Two methods that differ only in return type are not valid overloads — the compiler rejects that as ambiguous, because a call site like foo(); gives the compiler no information to disambiguate on. And a method's parameters are themselves local variables scoped to that method — reassigning a parameter inside a method body never affects the variable the caller passed in, whether that parameter is a primitive or a reference.
Example
Pass-by-value-of-the-reference, modeled in JS (which has the exact same semantics as Java here): mutating fields is visible to the caller, reassigning the parameter is not.
function mutateField(obj) {
obj.x = 999; // visible to the caller -- same object
}
function reassignParameter(obj) {
obj = { x: -1 }; // only changes the LOCAL copy of the reference
}
const point = { x: 1 };
mutateField(point);
console.log(point.x); // 999 -- the caller's object was mutated
reassignParameter(point);
console.log(point.x); // still 999 -- reassigning the parameter didn't touch the caller's variableTry it yourself
Add a second field y to point and mutate it inside mutateField -- confirm it's visible after the call.
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
Write resolveOverload(argType) modeling Java overload resolution among print(String), print(int), and print(double). Return 'String' for a 'string' argType, 'int' for 'int', and 'double' for 'double' or 'float' (float widens to double when no float overload exists).
Checks: string resolves to print(String) · int resolves to print(int) · float widens to print(double)
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
Write applyDiscount(price, percentOff) that returns a NEW discounted price without mutating any external state (model Java's pass-by-value: the function must be pure, taking primitives in and returning a new primitive out).
Checks: 20% off 100 is 80 · 0% off leaves the price unchanged · 50% off 200 is 100
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Expecting `void reset(Point p) { p = new Point(0, 0); }` to change the caller's variable -- it only reassigns the local parameter, never the caller's reference.
- Trying to overload two methods that differ only in return type -- the compiler rejects this; overloading requires a difference in the parameter list.
- Assuming the most 'specific-looking' overload always wins -- Java's actual resolution order (exact match, then widening, then autoboxing, then varargs) can pick a different overload than intuition suggests when types are close.
Knowledge check
Takeaway
Java always copies the argument's value into the parameter — for objects, that value is the reference, which is why mutating fields is visible to the caller but reassigning the parameter is not. Overloads must differ in their parameter list, never only in return type.
Summary
A method's signature is its name plus parameter types. Java is strictly pass-by-value; for reference types, the copied value is the reference, not the object. Overload resolution prefers an exact match, then widening, then autoboxing, then varargs, in that order.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.