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 condition
- map(Function)– Transforms each element
- flatMap(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 list
- forEach(Consumer)– Performs an action for each element
- reduce(...)– 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: 24List<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); // 9Can 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