Java supports functional programming aspects, but it is not a fully functional programming language.
Functions are first-class citizens — they can be stored in variables, pass them as arguments, return them from other functions.
Higher-order functions are functions that take other functions as arguments or return them.
Java lambdas (x -> x*2) and functional interfaces allow higher-order functions.
Functions are not fully first-class; they exist as objects via functional interfaces.
Example:
import java.util.function.Function;
public class Example {
public static void main(String[] args) {
Function<Integer, Integer> square = x -> x * x; // function as variable
applyFunction(5, square); // pass function
}
static int applyFunction(int n, Function<Integer, Integer> func) {
return func.apply(n);
}
}
Works, but you must use a Function<T,R> interface; no standalone function outside classes.
Data should be immutable by default; functions return new values instead of modifying existing ones.
Partially supported.
Use final variables, immutable collections (List.of(...)), or record types (Java 16+).
Example:
import java.util.List;
public class Example {
public static void main(String[] args) {
List<Integer> nums = List.of(1, 2, 3); // immutable list
// nums.add(4); → throws UnsupportedOperationException
List<Integer> doubled = nums.stream()
.map(x -> x * 2)
.toList(); // new list
}
}
Functions should have no side effects. Same input → same output.
Supported in style, not enforced. Lambdas can be pure, but Java allows side effects.
Example:
Function<Integer, Integer> square = x -> x * x; // pure
Function<Integer, Integer> impure = x -> {
System.out.println(x); // side effect
return x * x;
};
Focus on what to do, not how.
Streams API enables declarative operations on collections.
Example:
List<Integer> nums = List.of(1, 2, 3, 4, 5);
nums.stream()
.filter(x -> x % 2 == 0)
.map(x -> x * 2)
.forEach(System.out::println);
Compare with imperative style: using loops and mutable state.
Recursion often replaces loops; tail recursion is optimized in FP languages.
Java supports recursion, but no tail call optimization → deep recursion can cause stack overflow.
Example:
static int factorial(int n) {
if (n == 0) return 1;
return n * factorial(n - 1);
}
Expressions are only evaluated when needed.
Partially supported via Streams (intermediate operations are lazy).
Example:
List<Integer> nums = List.of(1, 2, 3, 4, 5);
nums.stream()
.map(x -> {
System.out.println("Mapping " + x);
return x * 2;
})
.filter(x -> x > 5); // nothing prints yet
nums.stream()
.map(x -> x*2)
.findFirst(); // triggers evaluation
Sum types and pattern matching allow concise handling of different variants of data.
Limited; pattern matching for instanceof and switch.
No native algebraic data types like in Haskell/Scala.
Example:
Object obj = "Hello";
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}
Combine simple functions into more complex ones without mutating state.
Supported via andThen / compose in Function<T,R>.
Example:
Function<Integer, Integer> add2 = x -> x + 2;
Function<Integer, Integer> square = x -> x * x;
// andThen → current function first, then the argument function
Function<Integer, Integer> add2ThenSquare = add2.andThen(square);
// compose → argument function first, then the current function
Function<Integer, Integer> squareAdd2 = add2.compose(square);
System.out.println(add2ThenSquare.apply(3)); // (3+2)*(3+2) = 25
System.out.println(squareAdd2.apply(3)); // (3*3)+2 = 11