Free Handbook · Every example compiled & verified

Methods

Java methods: parameters and return values, pass-by-value traced step by step, overloading, varargs, static vs instance, recursion, scope and final.

0 / 142 lessons🔥 0 day streak
ShareXLinkedIn

Module 04 · what you'll be able to do

  • Declare methods with parameters and a return type, and call them from main
  • Explain why Java is always pass-by-value, and predict what a method can and cannot change in its caller
  • Overload a method and write a varargs method that takes any number of arguments
  • Tell static methods from instance methods and fix "non-static method cannot be referenced"
  • Write a recursive method with a base case and trace how the call stack unwinds
01

Declaring and calling a method

A method is a named block of code that does one job. Every Java method lives inside a class. Its signature has, in order: modifiers (public, static), the return type (void if it returns nothing), the name, and a parameter list in parentheses. return ends the method and hands a value back to the caller.

java
//  modifiers   return type  name     parameters
    public static double       average(int a, int b) {
        return (a + b) / 2.0;    // the value handed back
    }

The anatomy of a method declaration. The parameters a and b are local variables that start with the values the caller passes in.

javaMain.java
public class Main {
    static void greet(String name) {           // void: returns nothing
        System.out.println("Hello, " + name);
    }

    static double average(int a, int b) {
        return (a + b) / 2.0;
    }

    static boolean isEven(int n) {
        return n % 2 == 0;                     // return a boolean expression directly
    }

    public static void main(String[] args) {
        greet("Asha");
        double avg = average(4, 7);            // store the result...
        System.out.println(avg);
        System.out.println(isEven(10) + " " + isEven(7));   // ...or use it inline
        System.out.println(average(1, 2) * 10);
    }
}
Outputcompiled & run with real Java
Hello, Asha
5.5
true false
15.0
Your turn

Write static int max3(int a, int b, int c) that returns the largest of three numbers and call it from main.

Name methods with verbs
calculateTotal, sendEmail, isValid, hasPermission. Methods returning boolean read best as questions (is…, has…, can…). A method that needs "and" in its name is doing two jobs — split it.
Error you will hit

missing return statement

java
public class Main {
    static int max(int a, int b) {
        if (a > b) {
            return a;
        } else if (b > a) {
            return b;
        }
    }

    public static void main(String[] args) {
        System.out.println(max(3, 7));
    }
}
Main.java:8: error: missing return statement
    }
    ^
1 error
error: compilation failed
Why the compiler said that

A non-void method must return a value on every path. You know a == b is the only case left, but the compiler does not reason about values: it sees an if / else if with no final else, so there is a path that falls off the end.

The fix

Make the last branch an unconditional else, or end the method with a return.

java
static int max(int a, int b) {
    if (a > b) {
        return a;
    }
    return b;          // covers b > a and a == b
}
02

Pass-by-value: what a method can change

Java is always pass-by-value: a method receives a copy of each argument. For a primitive, the copy is the number itself, so changing the parameter never touches the caller's variable. For an object or array, the value being copied is the reference (the arrow to the object). The method gets its own arrow pointing at the same object — so it can change the object's contents, but reassigning its own arrow does nothing to the caller's.

javaMain.java
import java.util.Arrays;

public class Main {
    static void addTen(int n) {
        n = n + 10;                       // changes the copy only
    }

    static void doubleFirst(int[] arr) {
        arr[0] = arr[0] * 2;              // follows the reference: changes the array
    }

    static void replace(int[] arr) {
        arr = new int[]{0, 0, 0};         // re-points the local copy only
    }

    public static void main(String[] args) {
        int x = 5;
        addTen(x);
        System.out.println(x);

        int[] nums = {5, 6, 7};
        doubleFirst(nums);
        System.out.println(Arrays.toString(nums));

        replace(nums);
        System.out.println(Arrays.toString(nums));
    }
}
Outputcompiled & run with real Java
5
[10, 6, 7]
[10, 6, 7]
VisualizeTwo arrows, one arrayStep 1 / 6
static void doubleFirst(int[] arr) {
arr[0] = arr[0] * 2;
}
static void replace(int[] arr) {
arr = new int[]{0, 0, 0};
}
int[] nums = {5, 6, 7};
doubleFirst(nums);
replace(nums);
System.out.println(Arrays.toString(nums));
Line 7

nums holds a reference to a new array object (call it A).

Variables now
nums→ A [5, 6, 7]
All 6 steps as a table
StepLineWhat happenedVariables now
17nums holds a reference to a new array object (call it A).nums = → A [5, 6, 7]
28Calling doubleFirst copies the reference: arr is a second arrow to the same array A.arr = → A
32arr[0] follows the arrow into A and writes 10. nums sees it, because it points at A too.nums = → A [10, 6, 7] arr = → A
49replace gets its own fresh copy of the reference, again pointing at A.arr = → A
55The parameter is re-pointed at a new array B. Only the local arrow moves; nums still points at A.arr = → B [0, 0, 0] nums = → A [10, 6, 7]
610Back in the caller, nums is unchanged by replace. B is now unreachable garbage.
So how does a method "return two things"?
It returns one value. If you need two, return a small object — in modern Java a record (module 05): record MinMax(int min, int max) {}. Mutating an argument to smuggle results out works, but callers do not expect it.
Quick check

static void rename(StringBuilder sb) { sb.append("!"); sb = new StringBuilder("new"); } — after var s = new StringBuilder("hi"); rename(s); what is s?

03

Overloading and varargs

Overloading means several methods with the same name but different parameter lists (number or types of parameters). The compiler picks one at compile time from the arguments you pass. The return type alone cannot distinguish overloads. System.out.println is the most famous example: there is one version for int, one for double, one for String, and so on.

javaMain.java
public class Main {
    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;
    }

    public static void main(String[] args) {
        System.out.println(area(4));
        System.out.println(area(3, 5));
        System.out.printf("%.2f%n", area(1.5));
        System.out.println(area('A'));     // char widens to int: 65 * 65
    }
}
Outputcompiled & run with real Java
16
15
7.07
4225

The last call is a trap worth knowing: no area(char) exists, so the compiler widens the char to int and picks area(int).

Varargs: any number of arguments

A parameter written int... nums accepts zero or more int arguments; inside the method, nums is an ordinary int[]. A varargs parameter must be the last one, and there can be only one. String.format and List.of are varargs methods.

javaMain.java
public class Main {
    static int sum(int... nums) {
        int total = 0;
        for (int n : nums) total += n;
        return total;
    }

    static String tag(String label, String... values) {
        return label + ": " + String.join(", ", values) + " (" + values.length + ")";
    }

    public static void main(String[] args) {
        System.out.println(sum());
        System.out.println(sum(5));
        System.out.println(sum(1, 2, 3, 4));
        System.out.println(sum(new int[]{10, 20}));   // an array works too
        System.out.println(tag("colors", "red", "green"));
    }
}
Outputcompiled & run with real Java
0
5
10
30
colors: red, green (2)
Your turn

Write static double max(double first, double... rest). Requiring one fixed parameter means it can never be called with zero arguments.

Error you will hit

method cannot be applied to given types

java
public class Main {
    static double average(int a, int b) {
        return (a + b) / 2.0;
    }

    public static void main(String[] args) {
        System.out.println(average(4, 7, 9));
    }
}
Main.java:7: error: method average in class Main cannot be applied to given types;
        System.out.println(average(4, 7, 9));
                           ^
  required: int,int
  found:    int,int,int
  reason: actual and formal argument lists differ in length
1 error
error: compilation failed
Why the compiler said that

The call must match a declared parameter list. required is what the method declares, found is what you passed. Read those two lines first; they usually make the fix obvious.

The fix

Pass the right arguments, add an overload for three numbers, or make the method varargs.

java
static double average(int... nums) {
    int total = 0;
    for (int n : nums) total += n;
    return (double) total / nums.length;
}
04

static vs instance methods

A static method belongs to the class: you call it as Math.max(a, b) or, inside the same class, just max(a, b). It has no this and cannot see instance fields. An instance method (no static) belongs to an object: you need one first, new Counter(), and call counter.increment(). It can read and change that object's fields. main is static because the JVM calls it before any object exists.

javaMain.java
public class Main {
    int count = 0;                      // instance field: one per object

    void increment() {                  // instance method: works on "this" object
        count++;
    }

    static int twice(int n) {           // static: needs no object
        return n * 2;
    }

    public static void main(String[] args) {
        System.out.println(twice(21));

        Main a = new Main();
        Main b = new Main();
        a.increment();
        a.increment();
        b.increment();
        System.out.println(a.count + " " + b.count);
        System.out.println(Math.abs(-7) + " " + Integer.parseInt("12"));
    }
}
Outputcompiled & run with real Java
42
2 1
7 12
Your turn

Add a static int created field and increment it in each increment() call. Print it: every object shares the one static field.

Error you will hit

non-static method cannot be referenced from a static context

java
public class Main {
    int square(int n) {
        return n * n;
    }

    public static void main(String[] args) {
        System.out.println(square(5));
    }
}
Main.java:7: error: non-static method square(int) cannot be referenced from a static context
        System.out.println(square(5));
                           ^
1 error
error: compilation failed
Why the compiler said that

main is static, so there is no current object. square is an instance method, so it needs one. This is the most common error in a beginner's first week of Java.

The fix

If the method does not use any instance fields (this one does not), make it static. If it does, create an object and call the method on it.

java
static int square(int n) {
    return n * n;
}
// or, keeping it an instance method:
// System.out.println(new Main().square(5));
05

Recursion and the call stack

A recursive method calls itself on a smaller version of the problem. It needs two parts: a base case that answers directly without recursing, and a recursive case that moves toward the base case. Each call gets its own stack frame with its own parameters; frames pile up until the base case returns, then unwind in reverse order.

javaMain.java
public class Main {
    static long factorial(int n) {
        if (n <= 1) return 1;              // base case
        return n * factorial(n - 1);       // recursive case: smaller n
    }

    static int sumDigits(int n) {
        if (n < 10) return n;
        return n % 10 + sumDigits(n / 10);
    }

    public static void main(String[] args) {
        System.out.println(factorial(4));
        System.out.println(factorial(20));
        System.out.println(sumDigits(2749));
    }
}
Outputcompiled & run with real Java
24
2432902008176640000
22
Visualizefactorial(4): down to the base case, then back upStep 1 / 9
static long factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
System.out.println(factorial(4));
Line 5

main calls factorial(4). A frame for n=4 is pushed.

Variables now
stackf(4)
All 9 steps as a table
StepLineWhat happenedVariables now
15main calls factorial(4). A frame for n=4 is pushed.stack = f(4)
23n=4 is not the base case, so it needs factorial(3) before it can multiply. It waits.stack = f(4) → f(3)
33n=3 waits on factorial(2).stack = f(4) → f(3) → f(2)
43n=2 waits on factorial(1). Four frames are now on the stack.stack = f(4) → f(3) → f(2) → f(1)
52n=1 hits the base case and returns 1. Its frame is popped.stack = f(4) → f(3) → f(2) returned = 1
63f(2) resumes: 2 * 1 = 2, returns.stack = f(4) → f(3) returned = 2
73f(3) resumes: 3 * 2 = 6, returns.stack = f(4) returned = 6
83f(4) resumes: 4 * 6 = 24, returns to main.stack = (empty) returned = 24
95main prints the result.
Error you will hit

StackOverflowError — recursion with no base case

java
public class Main {
    static long factorial(int n) {
        return n * factorial(n - 1);
    }

    public static void main(String[] args) {
        System.out.println(factorial(5));
    }
}
Exception in thread "main" java.lang.StackOverflowError
	at Main.factorial(Main.java:3)
	at Main.factorial(Main.java:3)
	at Main.factorial(Main.java:3)
	at Main.factorial(Main.java:3)
	...
Why the compiler said that

Without a base case, n goes 5, 4, 3, … 0, -1, -2 forever. Each call adds a stack frame, and the thread's stack (typically around 512 KB–1 MB) fills up after a few thousand to tens of thousands of frames. A stack trace of the same line repeated hundreds of times is the signature of runaway recursion.

The fix

Add a base case that every input eventually reaches. For deep inputs (say, a million), use a loop instead — Java does not optimise tail calls.

java
static long factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}
In real jobs
Recursion is natural for tree-shaped data: folders, JSON, org charts, parsing expressions. For linear work (summing a list, counting down) a loop is simpler and cannot overflow the stack. Module 13 uses recursion for trees and graphs.
06

Scope, final parameters and good method design

Scope is the region of code where a name exists. A local variable lives from its declaration to the closing } of the block it is declared in. Parameters are locals of the whole method. Two different methods can each have a variable called total; they never see each other. Java does not allow a local to shadow another local in an enclosing block of the same method.

javaMain.java
public class Main {
    static int total = 100;                 // a static field: visible to every method

    static int addTax(final int price) {    // final parameter: cannot be reassigned
        int total = price + price / 10;     // a local that hides the field
        return total;
    }

    public static void main(String[] args) {
        int result = addTax(200);
        System.out.println(result);
        System.out.println(total);          // the field, untouched

        for (int i = 0; i < 2; i++) {
            int square = i * i;             // exists only inside this loop body
            System.out.println(square);
        }
        // System.out.println(square);      // would not compile: out of scope
    }
}
Outputcompiled & run with real Java
220
100
0
1
Your turn

Inside addTax, print Main.total to reach the hidden field by its class name.

What final means on a parameter or local
final on a parameter or local only stops reassignment of that variable. It does not make an object immutable: a final int[] arr can still have arr[0] = 9. Many teams mark parameters final by convention; it documents that the method does not reuse them. Locals captured by a lambda must be final or "effectively final" (module 09).
  • One job per method. If you describe it with "and", split it.
  • Short. If it does not fit on one screen, extract helper methods with good names.
  • Few parameters. More than three or four is a sign they belong together in an object or record.
  • Return, do not print. A method that returns a value can be tested and reused; one that prints can only be read by a human.
  • No surprise side effects. A method called getTotal should not modify the array it was given.
Method signature
A method's name plus its parameter types. It is what the compiler uses to choose between overloads; the return type is not part of it.
Parameter vs argument
A parameter is the variable in the declaration; an argument is the value passed at the call site.
Pass-by-value
Every argument is copied into the parameter. For objects, the copied value is the reference.
Overloading
Several methods with the same name and different parameter lists, chosen at compile time.
Varargs
A last parameter written Type... name that accepts zero or more arguments as an array.
static method
A method that belongs to the class, has no this, and is called without an object.
Base case
The input a recursive method answers without calling itself. Without one, recursion never ends.
Call stack
The stack of frames, one per active method call, holding its parameters and locals.
Quick check

Which pair of methods is a legal overload?

Frequently asked questions

Is Java pass-by-value or pass-by-reference?
Always pass-by-value. For objects and arrays, the value that gets copied is the reference, so a method can change the object's contents but cannot make the caller's variable point at a different object.
What is the difference between a static and a non-static method in Java?
A static method belongs to the class and is called without an object (Math.max); it cannot use instance fields or this. A non-static (instance) method is called on an object and can read and change that object's fields.
Can two Java methods differ only by return type?
No. Overloads must differ in the number or types of their parameters. int parse(String s) and long parse(String s) in the same class is a compile error, because a call like parse("5") could not choose between them.

Finish the Java handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.