functional-programming
Functional Programming
Pure functional programming follows these principles:
No internal state or side effects
Uses pure functions (same input → same output)
Emphasizes higher-order functions (functions that take or return other functions)
"Functional programming is about writing functions that take inputs and return outputs without changing anything outside the function — no modifying global variables, just working with the arguments passed in."
When to use functional programming paradigm?
Use functional programming when you want to write cleaner, more concise code that focuses on what to do rather than how to do it, especially for processing collections or streams.
Intermediate Operations (lazy)
These are lazy operations that return a new stream and are executed only when a terminal operation is invoked.
filter(Predicate)
– Keeps elements that match a conditionmap(Function)
– Transforms each elementflatMap(Function)
– Flattens nested structures (e.g., Stream of Lists)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evensTimesTen = numbers.stream()
.filter(n -> n % 2 == 0) // intermediate: keep even numbers
.map(n -> n * 10) // intermediate: multiply each by 10
.collect(Collectors.toList()); // terminal: trigger processing
System.out.println(evensTimesTen); // Output: [20, 40, 60]
Terminal Operations (Eager)
These trigger the stream pipeline and produce a result or side effect.
collect(Collectors.toList())
– Collects the result into a listforEach(Consumer)
– Performs an action for each elementreduce(...)
– Reduces all elements into a single result
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
int product = numbers.stream()
.reduce(1, (a, b) -> a * b); // terminal: multiply all numbers
System.out.println("Product: " + product); // Output: Product: 24
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.forEach(name -> System.out.println("Hello " + name)); // terminal: print each
Function
.andThen
.compose
.apply
// 1 arguement 1 output
Function<Integer,Integer> incrementBy1 = num -> num + 1;
Function<Integer,Integer> multiplyBy2 = num -> num * 2;
int incrementThenMultiply = incrementBy1.andThen(multiplyBy2).apply(5);
System.out.println(incrementThenMultiply); // Output: 12 , (5+1)*2
int multiplyThenIncrement = incrementBy1.compose(multiplyBy2).apply(5);
System.out.println(multiplyThenIncrement); // Output: 11 , (5*2)+1
// 2 arguement 1 output
BiFunction<Integer,Integer,Integer> doubleOfSum = (num1,num2) -> (num1+num2)*2 ;
System.out.println(doubleOfSum.apply(2,3));
Consumer
.accept
List<Integer> nums = Arrays.asList(1,2,3,4,5,6,7,8,9);
Consumer<Integer> consumerInt = num -> System.out.println(num + 2);
consumerInt.accept(1);
BiConsumer<Integer,Integer> outputSum = (num1,num2) -> System.out.println(num1+num2);
outputSum.accept(5,4); // 9
Can be used as a callback too!
Predicate
Returns a boolean
List<Integer> nums = Arrays.asList(1,2,3,4,5,6,7,8,9);
Predicate<Integer> divisibleByTwoPredicate = num -> num%2 == 0;
nums.stream().filter(divisibleByTwoPredicate).forEach(System.out::println);
Supplier
Supplier is good because it lets you defer the creation of a value until it's actually needed, saving resources and improving efficiency.
"You want to define the logic now, but only execute it later, possibly under certain conditions."
import java.util.function.*;
public class Main {
public static String expensiveMethod(){
return "super_expensive_text";
}
public static void main(String[] args) {
Supplier<String> prepareExpensiveMethod = () -> expensiveMethod();
// ...do others
String expensiveText = prepareExpensiveMethod.get();
System.out.println(expensiveText); //super_expensive_text
}
}
Optionals
.isPresent
.orElseThrow
Some functions will return a Optional
object which means something or nothing.
Or we can use Optional to avoid NullPointerException
String name = Optional.ofNullable(getName()).orElse("Default Name");
Combinator pattern in functional programming
import java.util.function.Function;
// A simple User class
class User {
String name;
String email;
User(String name, String email) {
this.name = name;
this.email = email;
}
}
// A result type to hold validation results
enum ValidationResult {
SUCCESS,
NAME_EMPTY,
EMAIL_INVALID
}
// Functional interface for a validator
interface Validator extends Function<User, ValidationResult> {
// Combinator: combines two validators
default Validator and(Validator other) {
return user -> {
ValidationResult result = this.apply(user);
return result == ValidationResult.SUCCESS ? other.apply(user) : result;
};
}
}
// Main class with validation logic
public class CombinatorExample {
// Static validators
static Validator nameNotEmpty = user ->
(user.name == null || user.name.isEmpty()) ? ValidationResult.NAME_EMPTY : ValidationResult.SUCCESS;
static Validator emailContainsAt = user ->
(user.email != null && user.email.contains("@")) ? ValidationResult.SUCCESS : ValidationResult.EMAIL_INVALID;
public static void main(String[] args) {
// Combine validators using the combinator pattern
Validator isValidUser = nameNotEmpty.and(emailContainsAt);
User user = new User("Alice", "alice@example.com");
System.out.println("Validation result: " + isValidUser.apply(user)); // SUCCESS
User invalidUser = new User("", "aliceexample.com");
System.out.println("Validation result: " + isValidUser.apply(invalidUser)); // NAME_EMPTY
}
}
Last updated