Unlock Java’s Functional Power: Real‑World Examples and Performance Insights
This article explains functional programming concepts in Java, including immutability, stateless functions, higher‑order functions, closures, currying, recursion, lazy evaluation, and referential transparency, and demonstrates each with practical code samples and JMH performance benchmarks.
1. Introduction
Functional programming is a paradigm that treats functions as first‑class citizens, avoids mutable state, and emphasizes pure functions. It improves modularity, reusability, readability, and is useful for concurrency and mathematical computation.
Important rules
No data mutation – once an object is created it should not be changed; operations return new objects.
No implicit state – functions should not rely on hidden external state; all required data is passed explicitly as parameters.
Additional concepts applicable in Java include higher‑order functions, closures, currying, recursion, lazy evaluation, and referential transparency.
2. Practical examples
2.1 First‑class and higher‑order functions
Java does not fully support first‑class functions, but lambda expressions and the functional interfaces in java.util.function (Function, Consumer, Predicate, Supplier, etc.) provide similar capabilities.
<code>public class Test {
public static void main(String[] args) {
var list = Arrays.asList("Orange", "Apple", "Banana", "Grape", "XPack", "AKF");
var ret = calcLength(list, new FnFactory<String, Object>() {
public Object execute(final String it) {
return it.length();
}
});
System.err.printf("Length: %s%n", ret);
}
static <T, S> ArrayList<S> calcLength(List<T> arr, FnFactory<T, S> fn) {
var list = new ArrayList<S>();
arr.forEach(t -> list.add(fn.execute(t)));
return list;
}
@FunctionalInterface
public interface FnFactory<T, S> {
S execute(T it);
}
}
</code>Using built‑in functional interfaces simplifies the code:
<code>public class Test1 {
public static void main(String[] args) {
var list = Arrays.asList("Orange", "Apple", "Banana", "Grape", "XPack", "AKF");
var ret = calcLength(list, it -> it.length());
System.err.printf("Length: %s%n", ret);
}
static <T, S> ArrayList<S> calcLength(List<T> arr, Function<T, S> fn) {
var list = new ArrayList<S>();
arr.forEach(t -> list.add(fn.apply(t)));
return list;
}
}
</code>Closures and currying can be expressed with lambdas:
<code>public class ClosureTest {
Function<Integer, Integer> add(final int x) {
Function<Integer, Integer> partial = y -> x + y;
return partial;
}
public static void main(String[] args) {
ClosureTest closure = new ClosureTest();
var c1 = closure.add(100);
var c2 = closure.add(200);
System.out.println(c1.apply(66));
System.out.println(c2.apply(66));
}
}
</code>Result: 166 and 266.
Java’s Collections.sort method is another example of a higher‑order function.
<code>public static void main(String[] args) {
var list = Arrays.asList("Apple", "Orange", "Banana", "Grape");
Collections.sort(list, (String a, String b) -> a.compareTo(b));
System.err.printf("%s%n", list);
}
</code>2.2 Pure functions and benchmarking
Pure functions can be benchmarked with JMH. Example of an iterative factorial:
<code>@State(Scope.Thread)
public class FactorialTest {
@Param({"20"})
private long num;
@Benchmark
public long factorial() {
long result = 1;
for (; num > 0; num--) {
result *= num;
}
return result;
}
}
</code>Benchmark result: ~0.475 ns/op.
Recursive version:
<code>@State(Scope.Thread)
public class FactorialTest2 {
@Param({"20"})
private long num;
@Benchmark
public long factorialRec() {
return factorial(num);
}
private long factorial(long n) {
return n == 1 ? 1 : n * factorial(n - 1);
}
}
</code>Benchmark result: ~17.3 ns/op, slower due to recursion overhead.
Stream‑based recursion:
<code>@Benchmark
public long factorialRec() {
return LongStream.rangeClosed(1, num)
.reduce(1, (n1, n2) -> n1 * n2);
}
</code>Result is similar to the explicit recursive version.
2.3 Lazy evaluation
Java evaluates expressions eagerly, but logical operators ( && , || ) and the ternary operator ( ?: ) are short‑circuiting and thus lazily evaluated.
Eager example:
<code>public static void main(String[] args) {
System.out.println(addOrMultiply(true, add(4), multiply(4)));
System.out.println(addOrMultiply(false, add(4), multiply(4)));
}
public static int add(int x) {
System.out.println("executing add");
return x + x;
}
public static int multiply(int x) {
System.out.println("executing multiply");
return x * x;
}
public static int addOrMultiply(boolean add, int onAdd, int onMultiply) {
return add ? onAdd : onMultiply;
}
</code>Lazy version using lambdas:
<code>public static void main(String[] args) {
UnaryOperator<Integer> add = t -> { System.out.println("executing add"); return t + t; };
UnaryOperator<Integer> multiply = t -> { System.out.println("executing multiply"); return t * t; };
System.out.println(addOrMultiply(true, add, multiply, 4));
System.out.println(addOrMultiply(false, add, multiply, 4));
}
public static <T, R> R addOrMultiply(boolean add, Function<T, R> onAdd, Function<T, R> onMultiply, T t) {
return add ? onAdd.apply(t) : onMultiply.apply(t);
}
</code>Result shows only the needed function is executed.
2.4 Referential transparency
A function is referentially transparent when it can be replaced by its return value without changing program behavior. Using final variables and pure functions helps achieve this, though object mutation can still occur.
<code>final var list = Arrays.asList("Apple", "Orange"); // cannot reassign
// list = Arrays.asList("Pack", "XXXOOO"); // compile error
</code> <code>final var list = new ArrayList<>();
list.add("XXX"); // object state can still change
</code>Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.