Functional Programming in Java: From Lambdas to Stream API
Functional programming (FP) is a paradigm in which computations are represented as the evaluation of mathematical functions, avoiding state changes and data mutation. For a long time, Java remained a strictly object-oriented language, but with the release of Java 8 (2014), it took a huge step towards FP by adding lambda expressions, functional interfaces, and the Stream API. Today, these tools are indispensable for writing concise, readable, and thread-safe code.
In this article, we will break down the key concepts of functional programming in Java: from the basics of lambdas to advanced collection processing with the Stream API. The material is aimed at developers who are already familiar with basic Java syntax but want to master modern approaches.
1. Lambda Expressions and Functional Interfaces
A lambda expression is an anonymous function (a method without a name) that can be passed as an argument or stored in a variable. In Java, a lambda implements a functional interface — an interface with exactly one abstract method (SAM — Single Abstract Method).
Syntax:
(parameters) -> { body }Example: instead of creating an anonymous class for a comparator:
// Old way (Java 7)Comparator<String> comparator = new Comparator<String>() { @Override public int compare(String s1, String s2) { return s1.length() - s2.length(); }};
// New way (Java 8+)Comparator<String> comparatorLambda = (s1, s2) -> s1.length() - s2.length();Main functional interfaces in Java (package java.util.function):
Predicate<T>— checks a condition (methodtest).Function<T, R>— transforms an object of type T into R (methodapply).Consumer<T>— performs an action on an object (methodaccept).Supplier<T>— supplies an object without input parameters (methodget).
Example with Predicate:
Predicate<Integer> isEven = n -> n % 2 == 0;System.out.println(isEven.test(4)); // trueSystem.out.println(isEven.test(5)); // falseMethod References are a shorthand notation for a lambda when the body consists of a single method call:
// Lambdalist.forEach(item -> System.out.println(item));// Method Referencelist.forEach(System.out::println);2. Stream API: Basic Operations
The Stream API is a powerful tool for processing sequences of data (collections, arrays, files) in a declarative style. A Stream does not store data; it only describes a pipeline of operations. Data is not modified — each step creates a new stream.
Creating a Stream:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");Stream<String> stream = names.stream(); // sequentialStream<String> parallelStream = names.parallelStream(); // parallelIntermediate operations (return a new Stream):
filter(Predicate)— selects elements based on a condition.map(Function)— transforms each element.sorted()/sorted(Comparator)— sorting.distinct()— removes duplicates.limit(n)— limits the number of elements.
Terminal operations (finish the stream, return a result):
collect(Collectors.toList())— collects into a collection.forEach(Con