Skip to main content
Java beginner Lesson 8 of 58

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

ModifierVisible to
publicEveryone
protectedSame package + subclasses
privateSame 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:

FormSyntaxLambda equivalent
Static methodClassName::staticMethodx -> ClassName.staticMethod(x)
Instance on specific objectinstance::methodx -> instance.method(x)
Instance on arbitrary objectClassName::instanceMethod(obj, x) -> obj.method(x)
ConstructorClassName::newargs -> 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) { ... }

Frequently Asked Questions

What is the difference between a method and a function in Java?
In Java, all functions are methods — they must belong to a class. The word 'function' is often used informally, but technically Java has methods. The distinction matters when comparing with languages like Python or JavaScript where functions can exist outside classes.
When should I use a lambda instead of a named method?
Use a lambda for short, one-off logic passed to a method (sorting, filtering, event handling). Use a named method when the logic is reused in multiple places, needs unit testing by itself, or is more than 2-3 lines — named methods are easier to read and debug.
What is a functional interface?
A functional interface is any interface with exactly one abstract method. Examples from java.util.function: Predicate<T>, Function<T,R>, Consumer<T>, Supplier<T>. The @FunctionalInterface annotation enforces this at compile time. Lambdas can be assigned to any functional interface.
Can Java methods return multiple values?
Not directly. Common workarounds: return an array or List, return a custom object, return a Map.Entry, or use a record (Java 16+) like record Pair<A,B>(A first, B second) {} which is the cleanest approach.