Functional Programming in Java: Advanced Level
\n\nFunctional 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\n1. Function Composition and Currying
\n\nIn 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.
Composition example:
\nimport 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\nCurrying 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\nimport 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\nCurrying is useful for creating specialized functions based on common patterns, especially when working with configurations.
\n\n2. Advanced Work with Stream API
\n\nThe 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\n2.1. Parallel Streams and Spliterators
\nParallel 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.
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