Skip to content

04 · Methods & Parameters

A method is a named, reusable block of code. Methods live inside classes.

Defining and calling a method

public class MathUtils {

    // static method: belongs to the class itself, not an instance
    static int add(int a, int b) {
        return a + b;
    }

    public static void main(String[] args) {
        int result = add(2, 3);
        System.out.println(result);   // 5
    }
}

A method signature has: an access modifier (public, private, ...), an optional static, a return type (or void), a name, and a parameter list.

Parameters and return values

static double average(int[] numbers) {
    int sum = 0;
    for (int n : numbers) {
        sum += n;
    }
    return (double) sum / numbers.length;
}

static void printReport(String title) {
    System.out.println("Report: " + title);
    // no return statement needed -- return type is void
}

Method overloading

Multiple methods can share a name if their parameter lists differ (in count or type) — this is overloading, resolved at compile time.

static int multiply(int a, int b) {
    return a * b;
}

static double multiply(double a, double b) {
    return a * b;
}

static int multiply(int a, int b, int c) {
    return a * b * c;
}

System.out.println(multiply(2, 3));        // 6 (int version)
System.out.println(multiply(2.5, 4.0));    // 10.0 (double version)
System.out.println(multiply(2, 3, 4));      // 24 (three-arg version)

static vs. instance methods

A static method belongs to the class and can be called without an object. An instance method operates on a specific object's data and requires one to exist first.

public class Counter {
    private int count = 0;   // instance field

    // instance method -- needs a Counter object to call it
    void increment() {
        count++;
    }

    int getCount() {
        return count;
    }

    // static method -- called via Counter.describe(), no instance needed
    static String describe() {
        return "A simple counter class";
    }
}

Counter c = new Counter();
c.increment();
c.increment();
System.out.println(c.getCount());        // 2
System.out.println(Counter.describe());   // A simple counter class
Kind Called via Can access instance fields?
static method ClassName.method() No
instance method object.method() Yes

Variable scope

A variable declared inside a method (or block) only exists within that block.

static void demo() {
    int local = 10;
    if (local > 5) {
        int inner = local * 2;
        System.out.println(inner);   // 20 -- visible here
    }
    // inner is NOT visible here -- out of scope
}

How It Actually Works

Every method call pushes a new stack frame — its own local variable array, operand stack, and reference to the constant pool — onto the calling thread's call stack. Arguments are passed by value, always: for a primitive that's a copy of the bits; for an object reference that's a copy of the pointer, which is why mutating an object through a parameter is visible to the caller (same object) but reassigning the parameter itself is not (you only repointed your local copy).

Overload resolution (invokevirtual target selection when multiple methods share a name) happens entirely at compile time based on the static types of the arguments — the compiler picks the most specific applicable overload and bakes that exact method descriptor into the invoke* instruction's constant-pool entry. This is why passing null to two same-named overloads that both accept reference types is ambiguous at compile time even though it would be perfectly resolvable at runtime.

Recursive calls consume stack frames linearly; the JVM does not perform tail-call optimization (unlike some other JVM languages' own compilers), so deep unbounded recursion in Java always risks StackOverflowError — a real, fixed-size limit (-Xss, default around 512KB–1MB per thread) rather than a soft warning.

🔀 See this in another language

Exercise

Write a class Calculator with overloaded static methods add for two ints, two doubles, and three ints. Add an instance method remember(int value) that stores the last computed value in a field, and a getLastValue() method to retrieve it. Write a main method that exercises all of them.