Functional Java
Java 8 brought functional programming into the language — lambdas, method references, and the Stream API turned verbose loops into readable pipelines.
Lambdas & Functional Interfaces
A lambda is an anonymous function you can pass as a value. It works with any functional interface — an interface with exactly one abstract method, like Runnable, Comparator, or Function.
import java.util.function.*; // (params) -> body Function<Integer, Integer> square = x -> x * x; square.apply(5); // 25 BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b; add.apply(3, 4); // 7 Predicate<String> isEmpty = s -> s.isEmpty(); isEmpty.test(""); // true Consumer<String> printer = s -> System.out.println(s); printer.accept("hi"); // prints "hi" // Sorting with a lambda comparator List<String> names = new ArrayList<>(List.of("Bob", "Al", "Carol")); names.sort((a, b) -> a.length() - b.length()); // [Al, Bob, Carol]
Method References
When a lambda just calls one existing method, a method reference (::) says the same thing more concisely. s -> s.toUpperCase() becomes String::toUpperCase.
names.forEach(System.out::println); // instead of s -> System.out.println(s) names.stream() .map(String::toUpperCase) // instead of s -> s.toUpperCase() .sorted(Comparator.naturalOrder()) .forEach(System.out::println); // constructor reference Supplier<ArrayList<String>> makeList = ArrayList::new;
The Stream API
A stream is a pipeline of operations over a collection. Intermediate steps (filter, map, sorted) are lazy and chainable; a terminal step (collect, count, forEach) runs the whole pipeline once.
import java.util.stream.*; List<Integer> nums = List.of(1, 2, 3, 4, 5, 6); // filter -> map -> collect: even numbers, doubled, into a list List<Integer> result = nums.stream() .filter(n -> n % 2 == 0) // [2, 4, 6] .map(n -> n * 2) // [4, 8, 12] .collect(Collectors.toList()); // reduce / sum int sum = nums.stream().mapToInt(Integer::intValue).sum(); // 21 // group into a Map Map<Boolean, List<Integer>> byParity = nums.stream() .collect(Collectors.groupingBy(n -> n % 2 == 0)); // {false=[1,3,5], true=[2,4,6]} // count / anyMatch long big = nums.stream().filter(n -> n > 3).count(); // 3
.stream() again on the source collection to run a new pipeline.Optional
Optional<T> is a container that either holds a value or is empty — a type-level signal that a result might be absent, designed to reduce NullPointerException.
Optional<String> found = names.stream() .filter(n -> n.startsWith("C")) .findFirst(); // might be empty found.isPresent(); // true / false found.orElse("none"); // value, or "none" if empty found.ifPresent(System.out::println); // runs only if a value exists // map inside the Optional without null checks int len = found.map(String::length).orElse(0);
Optional<T> from a method that might not find a result, instead of returning null. The caller is then forced by the type to handle the empty case, which is exactly how NullPointerException gets designed out.