Functional Programming
Functional programming is a programming paradigm where programs are written using functions as the primary building blocks. Instead of focusing on state and sequential instructions (as in imperative programming), functional programming emphasizes declarative approaches, describing what to do rather than how to do it.
Functions as First-Class Citizens
Functions are treated as data. They can be assigned to variables, passed as arguments, and returned from other functions.
Example in Java:
Function<Integer, Integer> square = x -> x * x; System.out.println(square.apply(5)); // Output: 25
Immutability
Data is immutable, meaning it cannot be changed after it’s created. Instead, new copies are created whenever data is “modified.”
Example:
List<Integer> numbers = List.of(1, 2, 3); List<Integer> doubled = numbers.stream() .map(n -> n * 2) .toList(); System.out.println(doubled); // [2, 4, 6]
Side-Effect-Free Functions
Functions in functional programming are often pure functions, meaning they have no side effects (they don’t modify anything outside of the function).
Pure functions:
- Always return the same output for the same input.
- Don’t change global variables or state.
Example:
int add(int a, int b) { return a + b; // No side effects }
Declarative Style
- Programs describe what needs to be done, rather than specifying how to do it.
- Example:
Imperative (procedural):
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens = new ArrayList<>(); for (int n : numbers) { if (n % 2 == 0) { evens.add(n); } } System.out.println(evens);
Declarative (functional):
List<Integer> evens = numbers.stream() .filter(n -> n % 2 == 0) .toList(); System.out.println(evens);
Higher-Order Functions
Functions that either take other functions as arguments or return functions.
Example in Java:
Function<Integer, Function<Integer, Integer>> adder = x -> (y -> x + y); System.out.println(adder.apply(3).apply(5)); // Output: 8
Laziness (Lazy Evaluation)
Computations are only performed when the results are actually needed.
Streams in Java are a great example of this feature:
Stream<Integer> infiniteStream = Stream.iterate(1, n -> n + 1); List<Integer> firstFive = infiniteStream.limit(5).toList(); System.out.println(firstFive); // [1, 2, 3, 4, 5]
More Readable and Concise
- Less boilerplate code and a focus on declarative solutions make code easier to read.
Easier to Debug and Maintain
- Immutability and pure functions reduce complexity since state doesn’t change unpredictably.
Better for Parallelism
Functional programming makes it easier to implement parallel computations because data isn’t modified by multiple threads simultaneously.
Example with parallel streams:
List<Integer> numbers = List.of(1, 2, 3, 4, 5); numbers.parallelStream() .map(n -> n * n) .forEach(System.out::println);
Reduces Errors
- Pure functions and immutable data structures reduce the possibility of unintended side effects and bugs.
One practical application of functional programming in Java is the Streams API, which allows you to work with data in a declarative manner.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> uppercaseNames = names.stream()
.map(String::toUpperCase)
.filter(name -> name.startsWith("A"))
.toList();
System.out.println(uppercaseNames); // [ALICE]
The key functional interfaces from the java.util.function
package support functional programming:
- Consumer: Performs an action on a given input but doesn’t return anything.
- Supplier: Produces (returns) a value without taking any input.
- Function: Transforms input into output.
- Predicate: Tests a condition and returns
true
/false
.
Example with Predicate
and Function
:
Predicate<String> isShortName = name -> name.length() <= 3;
Function<String, String> greet = name -> "Hello, " + name;
System.out.println(isShortName.test("Bob")); // true
System.out.println(greet.apply("Alice")); // Hello, Alice
Functional programming in Java is about writing cleaner, more declarative, and efficient code using tools like lambda expressions, the Streams API, and functional interfaces. It introduces a new way of thinking that complements object-oriented programming and is especially powerful for handling modern, data-intensive applications.