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

\n\n

Functional programming (FP) in Java has ceased to be just a trendy trend — it is an integral part of the modern language, starting from version 8. For developers who have mastered basic lambdas and the Stream API, a stage begins when understanding deeper concepts is required: monads, function composition, lazy evaluation, and immutable data structures. This article is dedicated to advanced functional programming techniques in Java that will help write cleaner, more predictable, and thread-safe code.

\n\n

1. Function Composition and Currying

\n\n

In Java, the functional interfaces Function, Predicate, and Consumer provide default methods for composition: andThen() and compose(). This allows building transformation chains without creating intermediate variables.

\n\n

Composition example:

\n
import java.util.function.Function;\n\npublic class FunctionComposition {\n    public static void main(String[] args) {\n        Function<String, String> trim = String::trim;\n        Function<String, String> toUpper = String::toUpperCase;\n        Function<String, String> addExclamation = s -> s + "!";\n\n        // Composition: trim -> toUpper -> addExclamation\n        Function<String, String> process = trim.andThen(toUpper).andThen(addExclamation);\n\n        String result = process.apply("  hello world  ");\n        System.out.println(result); // HELLO WORLD!\n    }\n}
\n\n

Currying is the transformation of a function of several arguments into a sequence of functions of one argument. In Java, this can be emulated using nested lambdas:

\n\n
import java.util.function.Function;\n\npublic class CurryingExample {\n    // Regular function\n    static int add(int a, int b) { return a + b; }\n\n    // Curried version\n    static Function<Integer, Function<Integer, Integer>> curriedAdd = a -> b -> a + b;\n\n    public static void main(String[] args) {\n        // Partial application\n        Function<Integer, Integer> add5 = curriedAdd.apply(5);\n        System.out.println(add5.apply(3)); // 8\n        System.out.println(add5.apply(10)); // 15\n    }\n}
\n\n

Currying is useful for creating specialized functions based on common patterns, especially when working with configurations.

\n\n

2. Advanced Work with Stream API

\n\n

The Stream API in Java is not just a replacement for loops. Understanding its advanced capabilities allows you to significantly improve code performance and readability.

\n\n

2.1. Parallel Streams and Spliterators

\n

Parallel streams use ForkJoinPool. However, their efficiency depends on the data size and the nature of the operations. For custom parallelization control, you can use Spliterator.

\n\n
import java.util.Spliterator;\nimport java.util.stream.Stream;\nimport java.util.stream.StreamSupport;\n\npublic class CustomSpliterator {\n    public static void main(String[] args) {\n        // Creating a stream from an iterator with a custom spliterator\n        Spliterator<Integer> spliterator = new Spliterator<>() {\n            int current = 0;\n            @Override\n            public boolean tryAdvance(java.util.function.Consumer<? super Integer> action) {\n                if (current < 100) {\n                    action.accept(current++);\n                    return true;\n                }\n                return false;\n            }\n            @Override\n            public Spliterator<Integer> trySplit() {\n                // Simplest split\n                int mid = current + 50;\n                if (mid >= 100) return null;\n                Spliterator<Integer> prefix = new RangeSpliterator(curre                

Blogs

Book Recommendations