Functional Programming in Java: Advanced Techniques and Stream API

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

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

Blogs

Book Recommendations