Functions (Methods) in Java
Master Java methods — from basic declarations to lambda expressions, method references, and functional interfaces.
Methods — The Building Blocks of Behaviour
A method is a named block of code that performs a specific task. Methods let you write logic once and reuse it, making programs easier to read, test, and maintain.
// Basic method anatomy
returnType methodName(parameterType paramName, ...) {
// body
return value; // required if returnType is not void
}
public class Calculator {
// void — returns nothing
public void printResult(int result) {
System.out.println("Result: " + result);
}
// returns an int
public int add(int a, int b) {
return a + b;
}
// returns a double
public double divide(double numerator, double denominator) {
if (denominator == 0) {
throw new ArithmeticException("Cannot divide by zero");
}
return numerator / denominator;
}
public static void main(String[] args) {
Calculator calc = new Calculator();
int sum = calc.add(10, 25);
calc.printResult(sum); // Result: 35
System.out.println(calc.divide(10.0, 4.0)); // 2.5
}
}
Access Modifiers and Static Methods
| Modifier | Visible to |
|---|---|
public | Everyone |
protected | Same package + subclasses |
private | Same class only |
| (none) | Same package only |
public class MathUtils {
// static — belongs to the class, not an instance
// call as MathUtils.square(5), not new MathUtils().square(5)
public static int square(int n) {
return n * n;
}
public static int max(int a, int b) {
return a > b ? a : b;
}
// private helper — internal use only
private static boolean isEven(int n) {
return n % 2 == 0;
}
public static String parity(int n) {
return isEven(n) ? "even" : "odd";
}
}
// Usage
System.out.println(MathUtils.square(7)); // 49
System.out.println(MathUtils.max(12, 8)); // 12
System.out.println(MathUtils.parity(6)); // even
Parameters — Pass-by-Value
Java is always pass-by-value. For primitives, the method gets a copy. For objects, the method gets a copy of the reference (it can mutate the object’s state, but cannot reassign the original variable).
public class PassByValueDemo {
// primitives — original is unchanged
static void doubleIt(int x) {
x = x * 2;
System.out.println("Inside: " + x); // 20
}
// object reference — can mutate the object
static void addItem(java.util.List<String> list, String item) {
list.add(item); // modifies the actual list
}
public static void main(String[] args) {
int n = 10;
doubleIt(n);
System.out.println("After: " + n); // 10 — unchanged
var names = new java.util.ArrayList<String>();
addItem(names, "Alice");
System.out.println(names); // [Alice] — list was mutated
}
}
Varargs — Variable Argument Count
public class Stats {
// varargs — caller can pass any number of ints
public static int sum(int... numbers) {
int total = 0;
for (int n : numbers) total += n;
return total;
}
public static double average(double... values) {
if (values.length == 0) return 0.0;
double total = 0;
for (double v : values) total += v;
return total / values.length;
}
public static void main(String[] args) {
System.out.println(sum(1, 2, 3)); // 6
System.out.println(sum(10, 20, 30, 40)); // 100
System.out.println(average(4.0, 7.5, 2.5)); // 4.666...
}
}
Recursion
A method that calls itself. Always needs a base case to stop.
public class Recursion {
// factorial: n! = n * (n-1)! base case: 0! = 1
public static long factorial(int n) {
if (n <= 0) return 1; // base case
return n * factorial(n - 1); // recursive case
}
// Fibonacci with memoization (avoid exponential blowup)
private static java.util.Map<Integer, Long> memo = new java.util.HashMap<>();
public static long fib(int n) {
if (n <= 1) return n;
if (memo.containsKey(n)) return memo.get(n);
long result = fib(n - 1) + fib(n - 2);
memo.put(n, result);
return result;
}
public static void main(String[] args) {
System.out.println(factorial(10)); // 3628800
System.out.println(fib(50)); // 12586269025
}
}
Functional Interfaces
A functional interface has exactly one abstract method. Java’s java.util.function package provides the most common ones:
import java.util.function.*;
public class FunctionalDemo {
public static void main(String[] args) {
// Predicate<T> — takes T, returns boolean
Predicate<String> isLong = s -> s.length() > 5;
System.out.println(isLong.test("Hi")); // false
System.out.println(isLong.test("Hello World")); // true
// Function<T, R> — takes T, returns R
Function<String, Integer> wordCount = s -> s.split("\\s+").length;
System.out.println(wordCount.apply("one two three")); // 3
// Consumer<T> — takes T, returns void
Consumer<String> print = s -> System.out.println(">> " + s);
print.accept("Hello"); // >> Hello
// Supplier<T> — takes nothing, returns T
Supplier<Double> random = Math::random;
System.out.println(random.get()); // 0.something
// BiFunction<T, U, R> — takes two args, returns R
BiFunction<Integer, Integer, Integer> power = (base, exp) -> {
int result = 1;
for (int i = 0; i < exp; i++) result *= base;
return result;
};
System.out.println(power.apply(2, 10)); // 1024
// Composing functions
Function<Integer, Integer> times2 = x -> x * 2;
Function<Integer, Integer> plus3 = x -> x + 3;
Function<Integer, Integer> times2Plus3 = times2.andThen(plus3);
System.out.println(times2Plus3.apply(5)); // 13
}
}
Lambda Expressions
Lambdas are compact anonymous functions assignable to any functional interface:
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class LambdaDemo {
@FunctionalInterface
interface Transformer<T> {
T transform(T input);
}
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of("Charlie", "Alice", "Bob", "Dave"));
// Lambda — concise
names.sort((a, b) -> a.compareTo(b));
// Method reference — even shorter
names.sort(String::compareTo);
System.out.println(names); // [Alice, Bob, Charlie, Dave]
// Custom functional interface
Transformer<String> shout = s -> s.toUpperCase() + "!";
System.out.println(shout.transform("hello")); // HELLO!
// Predicate composition
Predicate<Integer> positive = n -> n > 0;
Predicate<Integer> even = n -> n % 2 == 0;
Predicate<Integer> positiveAndEven = positive.and(even);
List.of(-2, 0, 3, 4, 7, 8).stream()
.filter(positiveAndEven)
.forEach(System.out::println); // 4, 8
}
}
Method References
A cleaner syntax for lambdas that simply delegate to an existing method:
| Form | Syntax | Lambda equivalent |
|---|---|---|
| Static method | ClassName::staticMethod | x -> ClassName.staticMethod(x) |
| Instance on specific object | instance::method | x -> instance.method(x) |
| Instance on arbitrary object | ClassName::instanceMethod | (obj, x) -> obj.method(x) |
| Constructor | ClassName::new | args -> new ClassName(args) |
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class MethodRefDemo {
static int doubleIt(int n) { return n * 2; }
record Person(String name, int age) {}
public static void main(String[] args) {
// 1. Static method reference
Function<Integer, Integer> dbl = MethodRefDemo::doubleIt;
System.out.println(dbl.apply(7)); // 14
// 2. Instance method on a particular instance
String prefix = "Hello, ";
Function<String, String> greet = prefix::concat;
System.out.println(greet.apply("World")); // Hello, World
// 3. Instance method on an arbitrary instance of the type
Function<String, String> upper = String::toUpperCase;
System.out.println(upper.apply("java")); // JAVA
// 4. Constructor reference
BiFunction<String, Integer, Person> makePerson = Person::new;
Person p = makePerson.apply("Alice", 30);
System.out.println(p); // Person[name=Alice, age=30]
// Real-world: print a list
List<String> words = List.of("foo", "bar", "baz");
words.forEach(System.out::println);
// Collect to uppercase list
List<String> upperWords = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(upperWords); // [FOO, BAR, BAZ]
}
}
Writing Good Methods
Single Responsibility — one method, one job.
// Bad — method does validation, persistence, email, and inventory in one shot
public void processOrder(Order order) { /* everything mixed together */ }
// Good — each concern is isolated; processOrder orchestrates
public void processOrder(Order order) {
validateOrder(order);
saveToDatabase(order);
sendConfirmationEmail(order);
updateInventory(order);
}
Meaningful names — the name should say exactly what the method does:
// Bad
public List<User> get(boolean b, int x) { ... }
// Good
public List<User> getActiveUsersSince(LocalDate since) { ... }
Short parameter lists — more than 3-4 parameters signals a need for a parameter object:
// Bad
public void createAccount(String first, String last, String email,
String phone, String country, boolean admin) { ... }
// Good — introduce a record
record AccountRequest(String firstName, String lastName,
String email, String phone,
String country, boolean admin) {}
public void createAccount(AccountRequest request) { ... }