Methods and Parameters
|
This section documents the current Java release line, with Java 25 LTS as the reference point (no specific patch version is pinned), as published at the Java developer portal, the Java Tutorials, and the Java SE API specification — which are the references these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
A method is a named block of code that takes zero or more parameters and either returns a value or is
declared void. This page covers how methods are declared and called, how Java passes arguments, how the
compiler chooses between overloads, and the small utility APIs every method-heavy class leans on.
Declaring and Calling Methods
A method declaration has a return type, a name, a parenthesised parameter list, and a body. The
Defining Methods tutorial page describes
each part; Returning a Value from a
Method covers return and void.
class Geometry {
// instance method: returns a double
double circleArea(double radius) {
return Math.PI * radius * radius;
}
// void method: performs an action, returns nothing
void printBanner(String text) {
System.out.println("=== " + text + " ===");
return; // optional in a void method; exits early
}
// multiple parameters, each with its own type
int clamp(int value, int min, int max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
}
A void method may use a bare return; to exit early but cannot return a value; a non-void method must
return a value of the declared type (or a subtype) on every code path or the code will not compile.
static vs. instance methods
An instance method runs against a specific object and can read that object’s fields through this. A
static method belongs to the class itself, has no this, and is called through the class name. See
Understanding Class Members.
class Counter {
private int count; // instance field
void increment() { count++; } // instance method: needs an object
int value() { return count; }
static int sum(int... values) { // static: no object required
int total = 0;
for (int v : values) total += v;
return total;
}
}
var c = new Counter();
c.increment();
c.increment();
System.out.println(c.value()); // 2 -- called on an instance
System.out.println(Counter.sum(1, 2, 3)); // 6 -- called on the class
Use static for pure functions that do not depend on object state (factory methods, math helpers,
main); use instance methods for behaviour that acts on a particular object’s data.
Java Is Pass-by-Value
Java always passes arguments by value: the method receives a copy. For a primitive the copy is the value itself; for a reference type the copy is the reference (the pointer), not the object. Reassigning a parameter never affects the caller; following a copied reference to mutate the shared object does. This is spelled out in Passing Information to a Method or a Constructor.
import java.util.Arrays;
class PassByValue {
static void addOne(int n) {
n = n + 1; // changes only the local copy
}
static void reassign(int[] a) {
a = new int[] {9, 9, 9}; // rebinds the local copy of the reference
}
static void mutate(int[] a) {
a[0] = 99; // follows the reference: edits the shared array
}
public static void main(String[] args) {
int x = 10;
addOne(x);
System.out.println(x); // 10 -- unchanged
int[] nums = {1, 2, 3};
reassign(nums);
System.out.println(Arrays.toString(nums)); // [1, 2, 3] -- unchanged
mutate(nums);
System.out.println(Arrays.toString(nums)); // [99, 2, 3] -- changed
}
}
The rule to remember: a method can change what a passed object contains, but it can never change which object (or value) the caller’s variable refers to.
Overloading and Overload Resolution
Two or more methods in the same class may share a name as long as their parameter lists differ in number or type — this is overloading. The return type alone is not enough to distinguish overloads. The tutorial covers the idea under Defining Methods ("Overloading Methods").
static String describe(int n) { return "int: " + n; }
static String describe(long n) { return "long: " + n; }
static String describe(double n) { return "double: " + n; }
static String describe(Object o) { return "Object: " + o; }
static String describe(int... n) { return "varargs, count=" + n.length; }
describe(1); // "int: 1"
describe(1L); // "long: 1"
describe(1.0); // "double: 1.0"
describe("hi"); // "Object: hi"
describe(1, 2, 3); // "varargs, count=3"
The compiler picks one overload at compile time in three phases, defined in JLS 15.12.2:
-
without boxing/unboxing and without varargs;
-
allowing boxing/unboxing but still no varargs;
-
finally allowing varargs.
Within a phase the most specific applicable method wins (describe(int) is more specific than
describe(long), which is more specific than describe(Object)). Because varargs is only tried last,
describe(1) binds to describe(int), never to describe(int…). An ambiguous call — where no
candidate is strictly more specific — is a compile error.
Varargs methods
A trailing parameter written as Type… name accepts any number of arguments (including zero); inside
the method it is an array. See
Arbitrary Number of Arguments.
static int max(int first, int... rest) { // at least one argument required
int result = first;
for (int v : rest) {
if (v > result) result = v;
}
return result;
}
max(3); // 3 -- rest is a zero-length array
max(3, 7, 1, 9, 4); // 9
max(3, new int[] {7, 1}); // an array may be passed directly
Only one varargs parameter is allowed and it must come last. Prefer a required leading parameter (as above) when "zero arguments" would be meaningless, so the mistake is caught at compile time.
Recursion and the Call Stack
A recursive method calls itself, reducing the problem toward a base case that returns without recursing. Each call gets its own stack frame holding its parameters and locals.
static long factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1); // base case: n <= 1
}
static long gcd(long a, long b) { // Euclid's algorithm
return b == 0 ? a : gcd(b, a % b);
}
static void hanoi(int n, char from, char via, char to) {
if (n == 0) return; // base case
hanoi(n - 1, from, to, via);
System.out.println("move disk " + n + ": " + from + " -> " + to);
hanoi(n - 1, via, from, to);
}
factorial(5); // 120
gcd(48, 18); // 6
hanoi(3, 'A', 'B', 'C'); // 7 moves printed
If the base case is never reached, the stack keeps growing until the JVM throws
StackOverflowError:
static int noBaseCase(int n) {
return noBaseCase(n + 1) + 1; // recurses forever -> StackOverflowError
}
Java does not perform tail-call optimisation, so deep recursion (tens of thousands of frames) can exhaust
the stack even when logically correct. Convert such algorithms to an explicit loop or an explicit
java.util.Deque stack when depth is unbounded.
Utility APIs: Math and Objects
Two java.lang/java.util classes turn up in almost every method body.
java.lang.Math holds
static numeric helpers and the constants Math.PI and Math.E:
Math.max(3, 7); // 7
Math.min(3, 7); // 3
Math.abs(-4); // 4
Math.pow(2, 10); // 1024.0
Math.sqrt(2); // 1.4142135623730951
Math.floorMod(-1, 3); // 2 -- unlike -1 % 3, which is -1
Math.floorDiv(-7, 2); // -4
Math.multiplyExact(1_000_000, 1_000_000); // throws ArithmeticException on overflow
Math.round(2.5); // 3
The *Exact methods (addExact, subtractExact, multiplyExact, incrementExact, toIntExact) throw
ArithmeticException instead of silently wrapping around — use them for value-sensitive integer
arithmetic. Deeper numeric coverage is on Numbers and Math.
java.util.Objects
holds null-safe helpers, most useful at method entry and in equals/hashCode:
import java.util.Objects;
class Account {
private final String id;
private final String owner;
Account(String id, String owner) {
// fail fast on invalid input, with a clear message and NPE
this.id = Objects.requireNonNull(id, "id");
this.owner = Objects.requireNonNullElse(owner, "unknown");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Account other)) return false;
return Objects.equals(id, other.id) // null-safe: no NPE if a side is null
&& Objects.equals(owner, other.owner);
}
@Override
public int hashCode() {
return Objects.hash(id, owner); // combines fields into one hash
}
}
Key members: requireNonNull (throws
NullPointerException
early, with a message), requireNonNullElse / requireNonNullElseGet (supply a fallback),
equals(a, b) (treats two null`s as equal, never throws), `hash(…) (a varargs hash for several
fields), and toString(obj, nullDefault). The equals/hashCode contract these support is covered on
Inheritance and Polymorphism.
See Also
-
Classes and Objects — fields, constructors, and how instance methods fit into a class.
-
Inheritance and Polymorphism — overriding (distinct from the overloading covered here) and dynamic dispatch.
-
Lambdas and Method References — passing behaviour, not just data, into a method.
-
Numbers and Math — the full
MathandBigDecimalsurface.