skip to content

methods

parameters, return values, overloading and varargs.

chapter
chapter 02.09
reading time
7 min read

a method is a named block of code with parameters (inputs) and a return type (the one value it hands back). every call gets a fresh frame with fresh copies of the arguments. several methods can share a name if their parameter lists differ (overloading), and varargs (int... xs) lets one take any number of arguments, delivered as an array.

the picture

a method is a recipe card. the name is the title, the parameters are the ingredient list ("2 eggs, 1 cup flour"), the body is the instructions, and the return value is the dish you hand back. every time someone cooks from the card, they start with their own fresh ingredients (a new frame) and the card itself never changes.

{
}
parameters
click any part of the declaration. toggle the signature: the name plus parameter types is what java uses to tell methods apart. the return type isn't part of it.
Main.java
public class Main {
    static int max(int a, int b) {
        int bigger = a > b ? a : b;
        return bigger;
    }
 
    public static void main(String[] args) {
        IO.println(max(3, 7) + " " + max(-2, -9));
    }
}
output
7 -2

what a call actually does

calling a method pushes a new frame onto the call stack, copies each argument into the matching parameter, runs the body, and when it hits return, pops the frame and hands the value back to the caller, right in the middle of whatever expression made the call.

Main.java next to run
  1. 1public class Main {
  2. 2 static int square(int x) {
  3. 3 return x * x;
  4. 4 }
  5. 5
  6. 6 static int sumOfSquares(int a, int b) {
  7. 7 int s = square(a) + square(b);
  8. 8 return s;
  9. 9 }
  10. 10
  11. 11 public static void main(String[] args) {
  12. 12 int r = sumOfSquares(3, 4);
  13. 13 System.out.println(r);
  14. 14 }
  15. 15}
stdout
·
stack
main
no locals yet
heap
nothing allocated yet.

main is about to call sumOfSquares(3, 4).

step 1/10
a real recording from the JVM. step through and watch frames appear and disappear on the stack. square runs twice, and each call gets its own frame with its own x.

two things worth noticing. parameters are copies: square changing its x could never touch a in the caller (that's pass-by-value, and it holds for objects too, in a way that surprises people). and locals are gone the instant the method returns, which is why a method can't hand you a reference to its local int, only the value.

overloading

two methods can have the same name as long as their parameter lists differ: println(int), println(String), println(double)... that's why one println can print anything. the compiler picks which one to call from the types of the arguments, at compile time.

when more than one could fit, java searches in three rounds and stops at the first round that finds a match:

  1. phase 1: exact types, widening (int → long), subclasses. no boxing, no varargs.
  2. phase 2: also allow boxing and unboxing (int ↔ Integer).
  3. phase 3: also allow varargs.

within a round, the most specific candidate wins (int beats long beats double, Integer beats Object).

overloads =
call =
static String m(long x) { ... }
static String m(Integer x) { ... }
static String m(int... x) { ... }
m(5); // int
phase 1no boxingphase 2+ boxingphase 3+ varargs
javac picks m(long)

widening: int → long, no boxing needed, so it's found in phase 1. among the ones that fit, the most specific (narrowest) type wins.

toggle which overloads exist and which call to make. every combination here (63 sets of overloads × 9 calls) was actually compiled and run on jdk 25, so the pick and the errors are javac's own answers.
predict the output
java
static String m(long x)    { return "long"; }
static String m(Integer x) { return "Integer"; }
 
IO.println(m(5));

the return type plays no part in choosing, which is why you can't overload on it:

java
int f(int a) { return 1; }
long f(int a) { return 2; }

error: method f(int) is already defined. same name, same parameter types, same signature, whatever they return.

varargs

put ... after the last parameter's type and the method accepts any number of arguments of that type, including zero. inside the method, it's simply an array:

Main.java
void main() {
    IO.println(sum() + " " + sum(5) + " " + sum(1, 2, 3) + " " + sum(new int[] {4, 5}));
}
 
int sum(int... nums) {
    int total = 0;
    for (int n : nums) total += n;
    return total;
}
output
0 5 6 9
stack
sum(1, 2, 3)
nums
total0
main
no locals yet
heap
int[]
10
21
32
during sum(1, 2, 3): the compiler wrapped the three arguments in a new int[], and nums is a reference to it. sum() gets an empty array, never null.

only the last parameter can be varargs, and there can be just one. String.format(String format, Object... args) and List.of(E... elements) are the varargs methods you'll meet most.

keylines
  • a method: modifiers, return type, name, parameters, body. void returns nothing.
  • every call gets a new frame with fresh copies of the arguments. locals die on return.
  • the signature is name + parameter types. you can't overload on return type alone.
  • overload resolution: exact or widening first, then boxing, then varargs. most specific wins.
  • varargs T... arrives as an array (empty when there are no arguments). last parameter only.