What you'll learn
This module changes how you write Java. Functional programming treats behaviour as data — you can store a piece of logic in a variable, pass it to a method, and return it from one. Lambdas make this concise, and they're the foundation of the powerful Stream API coming next.
By the end you'll be able to:
- Understand what functional programming brings to Java
- Write lambda expressions fluently
- Use the built-in functional interfaces (
Predicate,Function,Consumer,Supplier) - Shorten lambdas with method references
- Compose and pass behaviour with higher-order functions
The idea: behaviour as data
Until now, data (numbers, strings, objects) went into methods and results came out. Functional programming adds a twist: the behaviour itself can be a value. Instead of writing a whole class to say "print this" or "is this even," you capture that logic in a lambda and hand it around like any other object. The result is code that's shorter, more flexible, and easier to read.
Lambda syntax
A lambda is an anonymous function — parameters, an arrow ->, and a body. It's a compact stand-in for a functional interface (Module 10). Compare the old anonymous class with the lambda:
// Before Java 8: a clunky anonymous class
Runnable oldWay = new Runnable() {
public void run() { System.out.println("Hello"); }
};
// With a lambda: the same thing, distilled
Runnable newWay = () -> System.out.println("Hello");
oldWay.run();
newWay.run();(a, b) -> a + bParameters
The inputs (types are usually inferred).
Arrow
Separates inputs from the body.
Body
The expression or block that runs.
Note
x -> x * 2); with a single expression you can drop the braces and return. For a multi-line body, use { ... return ...; }.The built-in functional interfaces
You rarely need to define your own functional interface — java.util.function provides the common shapes. Learn these four and you'll recognise them everywhere in modern Java:
| Interface | Method | Shape | Means |
|---|---|---|---|
Predicate<T> | test | T -> boolean | a yes/no test |
Function<T,R> | apply | T -> R | transform T into R |
Consumer<T> | accept | T -> void | do something with T |
Supplier<T> | get | () -> T | produce a T |
import java.util.function.*;
Predicate<Integer> isEven = n -> n % 2 == 0; // T -> boolean
Function<String, Integer> len = s -> s.length(); // T -> R
Consumer<String> printer = s -> System.out.println("-> " + s); // T -> void
Supplier<String> greet = () -> "Hello!"; // () -> T
System.out.println(isEven.test(4)); // true
System.out.println(len.apply("Java")); // 4
printer.accept("hi"); // -> hi
System.out.println(greet.get()); // Hello!Method references
When a lambda does nothing but call one existing method, a method reference (::) says the same thing even more concisely. x -> System.out.println(x) becomes System.out::println:
import java.util.*;
List<String> names = new ArrayList<>(List.of("charlie", "alice", "bob"));
names.forEach(System.out::println); // method reference (vs x -> println(x))
names.sort(String::compareTo); // reference to an instance method
System.out.println(names);Note
Integer::parseInt), bound instance (System.out::println), unbound instance (String::toUpperCase), and constructor (ArrayList::new). Use one whenever a lambda just forwards to a method.Higher-order functions
A higher-order function takes or returns another function. This is what makes functional code so composable — you can build complex behaviour by chaining simple pieces. Function offers andThen and compose; Predicate offers and, or, and negate:
import java.util.function.Function;
Function<Integer, Integer> doubler = x -> x * 2;
Function<Integer, Integer> increment = x -> x + 1;
// compose: run doubler, then increment
Function<Integer, Integer> combined = doubler.andThen(increment);
System.out.println(combined.apply(5)); // (5 * 2) + 1Effectively final
Recap & quick check
Key takeaways
- Functional programming treats behaviour as data you can store, pass, and return.
- A lambda is a concise anonymous function: (params) -> body, backed by a functional interface.
- Learn Predicate (test), Function (apply), Consumer (accept), Supplier (get).
- Method references (Class::method) shorten lambdas that just call one method.
- Higher-order functions take/return functions; captured local variables must be effectively final.
Quick check
1. What is a lambda expression?
2. Which functional interface represents a T -> boolean test?
3. What does System.out::println represent?
4. A local variable used inside a lambda must be…
5. What does Function's andThen do?
Excellent — you're thinking functionally now. Next up: Module 18 — the Stream API, where lambdas truly shine.