Methods
A method is a reusable block of code that performs a specific task. In Java, every piece of code lives inside a method, and every method lives inside a class.
Defining and calling methods
Until we cover classes and objects (next chapter), all our methods are static -- they belong to the class itself
rather than to an instance.
public class Greeter {
static String greet(String name) {
return "Hello, " + name + "!";
}
public static void main(String[] args) {
String message = greet("Ada");
System.out.println(message);
}
}
Result:
Hello, Ada!
Anatomy:
static-- belongs to the class, not to an instanceString-- the return typegreet-- the method name(String name)-- the parameter list (type + name)return-- sends a value back to the caller
void methods
Methods that do not return a value use void:
static void logMessage(String level, String text) {
System.out.println("[" + level + "] " + text);
}
public static void main(String[] args) {
logMessage("INFO", "Server started");
logMessage("WARN", "Disk usage high");
}
Result:
[INFO] Server started
[WARN] Disk usage high
Parameters and return types
Parameters are the inputs; the return type is the output. Java is strict about types:
static int add(int a, int b) {
return a + b;
}
static double divide(double a, double b) {
return a / b;
}
static boolean isEven(int number) {
return number % 2 == 0;
}
public static void main(String[] args) {
System.out.println(add(3, 4));
System.out.println(divide(10.0, 3.0));
System.out.println(isEven(7));
}
Result:
7
3.3333333333333335
false
If you declare a return type, you must return a value on every code path. The compiler enforces this:
// Compile error: missing return statement
static int broken(int x) {
if (x > 0) {
return x;
}
// what about x <= 0? The compiler won't allow this.
}
Early return (guard clauses)
Return early to handle edge cases at the top of a method:
static String classify(int score) {
if (score < 0 || score > 100) {
return "Invalid score";
}
if (score >= 90) {
return "A";
}
if (score >= 80) {
return "B";
}
if (score >= 70) {
return "C";
}
return "F";
}
public static void main(String[] args) {
System.out.println(classify(95));
System.out.println(classify(82));
System.out.println(classify(55));
System.out.println(classify(-1));
}
Result:
A
B
F
Invalid score
Guard clauses keep code flat instead of deeply nested. Prefer them over large if-else chains.
Method overloading
Multiple methods can share the same name if their parameter lists differ (different number or types of parameters):
static int add(int a, int b) {
return a + b;
}
static int add(int a, int b, int c) {
return a + b + c;
}
static double add(double a, double b) {
return a + b;
}
public static void main(String[] args) {
System.out.println(add(1, 2)); // calls int add(int, int)
System.out.println(add(1, 2, 3)); // calls int add(int, int, int)
System.out.println(add(1.5, 2.5)); // calls double add(double, double)
}
Result:
3
6
4.0
The compiler decides which overload to call based on the argument types. This is called compile-time polymorphism.
Note: you cannot overload by return type alone -- the parameter list must differ.
Varargs
Accept a variable number of arguments using ...:
static int sum(int... numbers) {
int total = 0;
for (int n : numbers) {
total += n;
}
return total;
}
public static void main(String[] args) {
System.out.println(sum(1, 2, 3));
System.out.println(sum(10, 20, 30, 40));
System.out.println(sum());
}
Result:
6
100
0
Inside the method, numbers is an int[] (array). Rules:
- Only one varargs parameter per method
- It must be the last parameter
static void log(String level, String... messages) {
for (String msg : messages) {
System.out.println("[" + level + "] " + msg);
}
}
public static void main(String[] args) {
log("INFO", "Server started", "Listening on port 8080");
}
Result:
[INFO] Server started
[INFO] Listening on port 8080
Pass-by-value
Java is always pass-by-value. For primitives, the value is copied. For objects, the reference is copied (not the object itself).
Primitives are copied
static void tryToChange(int x) {
x = 999;
System.out.println("Inside method: " + x);
}
public static void main(String[] args) {
int value = 42;
tryToChange(value);
System.out.println("After method: " + value);
}
Result:
Inside method: 999
After method: 42
The original value is unchanged -- the method received a copy.
Object references are copied
static void addItem(java.util.List<String> list) {
list.add("added by method");
}
public static void main(String[] args) {
var items = new java.util.ArrayList<String>();
items.add("original");
addItem(items);
System.out.println(items);
}
Result:
[original, added by method]
The method receives a copy of the reference -- it points to the same list object. So modifications to the object are visible to the caller. But reassigning the reference inside the method does not affect the caller:
static void reassign(java.util.List<String> list) {
list = new java.util.ArrayList<>(); // this only changes the local copy
list.add("new list");
System.out.println("Inside: " + list);
}
public static void main(String[] args) {
var items = new java.util.ArrayList<String>();
items.add("original");
reassign(items);
System.out.println("After: " + items);
}
Result:
Inside: [new list]
After: [original]
Scope
Variables are only accessible within the block where they are declared:
static void example() {
int x = 10;
if (x > 5) {
int y = 20;
System.out.println(x + y); // both accessible here
}
// System.out.println(y); // compile error: y is not accessible here
System.out.println(x); // x is still accessible
}
Result:
30
10
Method parameters are scoped to the method body. Variables declared in for loops are scoped to the loop:
for (int i = 0; i < 3; i++) {
System.out.println(i);
}
// System.out.println(i); // compile error: i is not accessible here
Recursion
A method that calls itself:
static int factorial(int n) {
if (n <= 1) {
return 1; // base case
}
return n * factorial(n - 1); // recursive case
}
public static void main(String[] args) {
System.out.println(factorial(5)); // 5 * 4 * 3 * 2 * 1
System.out.println(factorial(0));
System.out.println(factorial(10));
}
Result:
120
1
3628800
Every recursive method needs:
- A base case that stops the recursion
- A recursive case that moves toward the base case
Without a base case, the method calls itself forever and throws StackOverflowError.
Fibonacci
static int fibonacci(int n) {
if (n <= 0) return 0;
if (n == 1) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
public static void main(String[] args) {
for (int i = 0; i <= 10; i++) {
System.out.print(fibonacci(i) + " ");
}
System.out.println();
}
Result:
0 1 1 2 3 5 8 13 21 34 55
This naive implementation is slow for large n because it recalculates the same values repeatedly. For production code,
use iteration or memoization.
Practical example: utility methods
A common pattern is to group related utility methods in a class:
public class StringUtils {
static boolean isNullOrEmpty(String s) {
return s == null || s.isEmpty();
}
static String capitalize(String s) {
if (isNullOrEmpty(s)) {
return s;
}
return Character.toUpperCase(s.charAt(0)) + s.substring(1);
}
static String repeat(String s, int times) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < times; i++) {
sb.append(s);
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(isNullOrEmpty(""));
System.out.println(isNullOrEmpty("hello"));
System.out.println(capitalize("java"));
System.out.println(repeat("ab", 3));
}
}
Result:
true
false
Java
ababab
Summary
- Methods have a return type, name, and parameter list. Use
voidfor no return value. staticmethods belong to the class -- we will see instance methods in the next chapter.- Guard clauses (early returns) keep methods flat and readable.
- Overloading lets multiple methods share a name with different parameter lists.
- Varargs (
type... name) accept a variable number of arguments as an array. - Java is pass-by-value -- primitives are copied, object references are copied (but the object they point to is shared).
- Scope is block-level -- variables exist only within their enclosing
{}. - Recursion requires a base case to avoid
StackOverflowError.
Next up: Classes & Objects -- the heart of Java's object-oriented programming.