Functional Programming in Java: Advanced Level
Functional programming (FP) is a paradigm in which computations are treated as evaluation of mathematical functions, avoiding changing state and mutable data. Java, being an object-oriented language, gained powerful tools for FP with the release of Java 8: lambda expressions, Stream API, and functional interfaces. At an advanced level, these capabilities allow writing more concise, safe, and expressive code, especially when working with collections, asynchronous operations, and error handling.
In this article, we will explore advanced functional programming techniques in Java: function composition, monads (Optional and CompletableFuture), Stream API with parallel processing, and currying.
1. Function Composition and Functional Interfaces
In Java, functional interfaces are interfaces with a single abstract method (SAM). The most commonly used ones are: Function<T, R>, Predicate<T>, Consumer<T>, and Supplier<T>. Advanced usage includes function composition using the andThen() and compose() methods.
Example: transformation chain
import java.util.function.Function;
public class FunctionComposition { public static void main(String[] args) { // Function: string -> its length Function<String, Integer> lengthFunction = String::length; // Function: number -> its square Function<Integer, Integer> squareFunction = x -> x * x;
// Composition: string -> length -> square of length Function<String, Integer> composedFunction = lengthFunction.andThen(squareFunction);
String text = "Hello, Java!"; int result = composedFunction.apply(text); // 12 * 12 = 144 System.out.println("Result: " + result); // 144 }}The andThen() method applies the first function, then the second. compose() does the opposite: first applies the passed function, then the first one. This allows building flexible and reusable transformation chains.
Practical example: filtering and transforming a list
import java.util.List;import java.util.function.Function;import java.util.function.Predicate;import java.util.stream.Collectors;
public class PredicateAndFunction { public static void main(String[] args) { List<String> names = List.of("Alice", "Bob", "Charlie", "David", "Eve");
Predicate<String> startsWithA = s -> s.startsWith("A"); Function<String, String> toUpperCase = String::toUpperCase;
List<String> result = names.stream() .filter(startsWithA) .map(toUpperCase) .collect(Collectors.toList());
System.out.println(result); // [ALICE] }}2. Monads in Java: Optional and CompletableFuture
A monad is a pattern that encapsulates a value and provides operations for transforming it without explicit unwrapping. In Java, monads are implemented through Optional (for handling null) and CompletableFuture (for asynchronous operations).
Optional as a monad
The map(), flatMap(), and filter() methods allow chaining operations on a potentially absent value.
import java.util.Optional;
public class OptionalMonad { public static void main(String[] args) { // Create an Optional with a non-empty value Optional<String> optional = Optional.of("Hello");
// Transformation chain String result = optional .filter(s -> s.length() > 3) // filter .m