Functional Programming in Java: From A to Z
Functional programming (FP) is a paradigm in which computation is treated as the evaluation of mathematical functions, avoiding state changes and mutable data. Java, traditionally an imperative language, has been actively incorporating FP elements since version 8. This allows writing more concise, predictable, and easily testable code, especially when working with collections and asynchronous operations.
In this article, we will break down the key concepts of FP in Java: from lambda expressions to advanced techniques for working with the Stream API and Optional. You will learn how to apply these tools in practice to make your code cleaner and more efficient.
1. Basics: Lambda Expressions and Functional Interfaces
Lambda expression is a concise way to represent an anonymous method (function). It allows passing behavior as data. Syntax: (parameters) -> body.
Functional interface is an interface with a single abstract method (SAM — Single Abstract Method). These are the interfaces that can be implemented using lambdas. Key built-in interfaces in java.util.function:
Predicate<T>— accepts an object T, returns a boolean (methodtest).Function<T, R>— accepts T, returns R (methodapply).Consumer<T>— accepts T, returns nothing (methodaccept).Supplier<T>— accepts nothing, returns T (methodget).UnaryOperator<T>— a special case of Function where T and R are the same type.
Example: Sorting a list with Comparator (functional interface)
import java.util.*;
public class LambdaExample { public static void main(String[] args) { List<String> names = Arrays.asList("Ivan", "Anna", "Petr", "Maria");
// Without lambda (anonymous class) Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.length() - b.length(); } });
// With lambda Collections.sort(names, (a, b) -> a.length() - b.length());
// Even shorter: using Comparator.comparingInt names.sort(Comparator.comparingInt(String::length));
System.out.println(names); // [Anna, Petr, Ivan, Maria] }}Example: Using Predicate for filtering
import java.util.function.Predicate;
public class PredicateExample { public static void main(String[] args) { Predicate<Integer> isEven = n -> n % 2 == 0; Predicate<Integer> isPositive = n -> n > 0;
System.out.println(isEven.test(4)); // true System.out.println(isEven.and(isPositive).test(-2)); // false (not positive) System.out.println(isEven.or(isPositive).test(-3)); // true (not even, but positive? no, -3 is not positive) }}2. Stream API: A Powerful Data Processing Tool
Stream API is a sequence of elements supporting parallel and sequential operations. A Stream does not store data, but transforms it. Key operations are divided into intermediate (return a new Stream) and terminal (complete processing and return a result or side effect).
Basic Stream API operations:
- Intermediate:
filter,map,flatMap,sorted,distinct,peek,limit,skip. - Terminal:
collect