Pass behaviour as values with lambdas and functional interfaces, then filter, map, and reduce collections cleanly with streams.
Why: a lambda is a short, anonymous function you can store in a variable or pass to a method. The syntax is parameters -> body. It lets you hand a piece of behaviour to other code instead of just data.
import java.util.function.Function;
// (input) -> output
Function<Integer, Integer> doubler = n -> n * 2;
System.out.println(doubler.apply(5)); // 10Why: a lambda needs a type — that type is a functional interface, an interface with exactly one method. Java provides ready-made ones: Predicate (returns boolean), Function (transforms a value), Consumer (takes a value, returns nothing), Supplier (provides a value).
import java.util.function.Predicate;
import java.util.function.Consumer;
Predicate<String> isLong = s -> s.length() > 5;
Consumer<String> shout = s -> System.out.println(s.toUpperCase());
System.out.println(isLong.test("hello")); // false
shout.accept("hi"); // "HI"Why: when a lambda just calls one existing method, a method reference (Class::method) says the same thing with less noise. System.out::println means "call println on each item."
import java.util.List;
List<String> names = List.of("Ada", "Alan", "Grace");
names.forEach(System.out::println); // same as: name -> System.out.println(name)Why: a stream is a pipeline over a collection. You .stream() it, chain operations like filter (keep some) and map (transform each), then collect the result into a new list. The original collection is never changed.
import java.util.List;
import java.util.stream.Collectors;
List<String> names = List.of("Ada", "Alan", "Grace", "Al");
List<String> result = names.stream()
.filter(n -> n.length() > 2) // drop "Al"
.map(String::toUpperCase) // ADA, ALAN, GRACE
.collect(Collectors.toList());
System.out.println(result); // [ADA, ALAN, GRACE]Why: terminal operations end a stream and produce one result. count(), sum(), and reduce() collapse many elements into a single answer — totals, counts, maximums.
import java.util.List;
List<Integer> prices = List.of(10, 25, 5, 40);
int total = prices.stream()
.filter(p -> p > 8)
.mapToInt(Integer::intValue)
.sum();
System.out.println(total); // 75 (10 + 25 + 40)Why: small functions can be combined into bigger ones. andThen runs one function, then feeds its result into the next — building a pipeline from reusable pieces instead of one tangled method.
import java.util.function.Function;
Function<Integer, Integer> addOne = n -> n + 1;
Function<Integer, Integer> square = n -> n * n;
Function<Integer, Integer> addThenSquare = addOne.andThen(square);
System.out.println(addThenSquare.apply(4)); // (4 + 1)^2 = 25