Skip to content
IRC-CodingIRC-Coding
Functional ProgrammingLambda ExpressionsFunctional InterfacesHigher Order FunctionsStreamsProgramming LanguageProgramming

Functional Programming: Lambda Expressions & Interfaces

Master functional programming with lambda expressions, functional interfaces, higher-order functions, pure functions, immutability, and streams.

S

schutzgeist

10 min read
Functional Programming: Lambda Expressions & Interfaces

Functional Programming: Lambda Expressions & Functional Interfaces

This guide covers functional programming in depth, including lambda expressions, Functional Interfaces, and Higher-Order Functions with practical examples.

In a Nutshell

Functional programming prioritizes computation through functions rather than state mutations. Lambda expressions are inline function literals that bind to Functional Interfaces.

Core Concepts

Functional Programming is a paradigm that treats functions as first-class building blocks. Unlike imperative programming, it avoids state changes.

Lambda Expressions represent anonymous behavior with parameters, a body, and an optional return type. The compiler infers the type from the target context.

Functional Interfaces in Java have exactly one abstract method and enable Higher-Order Functions:

  • Predicate: boolean test(T t) – test conditions
  • Function: R apply(T t) – transformations
  • Consumer: void accept(T t) – consume values
  • Supplier: T get() – produce values

Key Principles:

  • Pure Functions: immutable input → deterministic output
  • Immutability: data never changes
  • Referential Transparency: a function call can be replaced with its result
  • Higher-Order Functions: functions accept or return other functions

Exam Highlights

  • Lambda Expressions: anonymous functions with concise syntax
  • Functional Interfaces: exactly one abstract method
  • Higher-Order Functions: functions as parameters or return values
  • Pure Functions: no side effects, deterministic behavior
  • Immutability: unchangeable data structures
  • Streams API: declarative data processing
  • Method References: concise syntax for method calls
  • Industry-Relevant: modern Java and functional approaches

Building Blocks

  1. Lambda Expressions: (x, y) -> x + y
  2. Functional Interfaces: Predicate<T>, Function<T,R>
  3. Pure Functions: no side effects
  4. Immutability: unchangeable objects
  5. Higher-Order Functions: map(), filter(), reduce()
  6. Streams: sequential data processing
  7. Method References: String::length
  8. Closures: accessing outer scope variables

Practical Examples

1. Lambda Expressions and Functional Interfaces in Java

import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;

public class FunctionalProgrammingDemo {
    
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // Lambda with Predicate (Filter)
        Predicate<String> longerThanFour = name -> name.length() > 4;
        List<String> longNames = names.stream()
            .filter(longerThanFour)
            .collect(Collectors.toList());
        System.out.println("Long names: " + longNames);
        
        // Lambda with Function (Map/Transformation)
        Function<String, Integer> stringLength = String::length; // Method Reference
        List<Integer> lengths = names.stream()
            .map(stringLength)
            .collect(Collectors.toList());
        System.out.println("Name lengths: " + lengths);
        
        // Lambda with Consumer (ForEach)
        Consumer<String> printer = name -> System.out.println("Hello " + name);
        names.forEach(printer);
        
        // Lambda with Supplier (Generate)
        Supplier<Double> randomNumber = () -> Math.random();
        System.out.println("Random number: " + randomNumber.get());
        
        // Complex Lambda Expressions
        Predicate<Integer> isEven = n -> n % 2 == 0;
        Predicate<Integer> isGreaterThanFive = n -> n > 5;
        
        // Combining Predicates
        Predicate<Integer> isEvenAndGreater = isEven.and(isGreaterThanFive);
        
        List<Integer> filteredNumbers = numbers.stream()
            .filter(isEvenAndGreater)
            .collect(Collectors.toList());
        System.out.println("Even and >5: " + filteredNumbers);
        
        // Higher-Order Function
        Function<Integer, Predicate<Integer>> greaterThan = threshold -> 
            num -> num > threshold;
        
        Predicate<Integer> greaterThanThree = greaterThan.apply(3);
        List<Integer> largerNumbers = numbers.stream()
            .filter(greaterThanThree)
            .collect(Collectors.toList());
        System.out.println(">3: " + largerNumbers);
    }
}

2. Pure Functions and Immutability

// Imperative Approach (with side effects)
class ImperativeCalculator {
    private int sum = 0;
    
    public void add(int value) {
        this.sum += value; // Side effect: state changes
    }
    
    public int getSum() {
        return sum;
    }
}

// Functional Approach (Pure Functions)
class FunctionalCalculator {
    
    // Pure Function: no side effects, deterministic
    public static int add(int a, int b) {
        return a + b;
    }
    
    // Pure Function with immutable data
    public static List<Integer> filterEven(List<Integer> numbers) {
        return numbers.stream()
            .filter(n -> n % 2 == 0)
            .collect(Collectors.toList());
    }
    
    // Pure Function with transformation
    public static List<Integer> square(List<Integer> numbers) {
        return numbers.stream()
            .map(n -> n * n)
            .collect(Collectors.toList());
    }
    
    // Higher-Order Function
    public static List<Integer> process(List<Integer> numbers, 
                                         Function<Integer, Integer> operation) {
        return numbers.stream()
            .map(operation)
            .collect(Collectors.toList());
    }
    
    // Pure Function with composition
    public static Function<Integer, Integer> multiplyBy(int factor) {
        return num -> num * factor;
    }
    
    public static Function<Integer, Integer> addTo(int value) {
        return num -> num + value;
    }
}

// Immutable Data Class
public final class Person {
    private final String name;
    private final int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // Pure Function for changes (creates new object)
    public Person withAge(int newAge) {
        return new Person(this.name, newAge);
    }
    
    public Person withName(String newName) {
        return new Person(newName, this.age);
    }
    
    // Getters (no setters for immutability)
    public String getName() { return name; }
    public int getAge() { return age; }
    
    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

// Usage
public class PureFunctionDemo {
    public static void main(String[] args) {
        // Imperative Approach
        ImperativeCalculator imperative = new ImperativeCalculator();
        imperative.add(5);
        imperative.add(3);
        System.out.println("Imperative: " + imperative.getSum()); // 8
        
        // Functional Approach
        int result1 = FunctionalCalculator.add(5, 3);
        int result2 = FunctionalCalculator.add(5, 3); // Always same result
        
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> even = FunctionalCalculator.filterEven(numbers);
        List<Integer> squares = FunctionalCalculator.square(numbers);
        
        System.out.println("Even: " + even);
        System.out.println("Squares: " + squares);
        
        // Higher-Order Function
        Function<Integer, Integer> double_ = n -> n * 2;
        List<Integer> doubled = FunctionalCalculator.process(numbers, double_);
        System.out.println("Doubled: " + doubled);
        
        // Function Composition
        Function<Integer, Integer> multiply = FunctionalCalculator.multiplyBy(2);
        Function<Integer, Integer> add = FunctionalCalculator.addTo(10);
        Function<Integer, Integer> combined = multiply.andThen(add);
        
        List<Integer> combinedResult = FunctionalCalculator.process(numbers, combined);
        System.out.println("Combined (x*2+10): " + combinedResult);
        
        // Immutability
        Person alice = new Person("Alice", 25);
        Person aliceOlder = alice.withAge(26);
        
        System.out.println("Original: " + alice);      // Alice (25)
        System.out.println("Changed: " + aliceOlder); // Alice (26)
    }
}

3. Streams API and Declarative Programming

import java.util.*;
import java.util.stream.*;

public class StreamAPIDemo {
    
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 25, "Engineering"),
            new Person("Bob", 30, "Marketing"),
            new Person("Charlie", 35, "Engineering"),
            new Person("Diana", 28, "Sales"),
            new Person("Eve", 32, "Engineering")
        );
        
        // Declarative data processing with Streams
        
        // 1. Filter and transform
        List<String> engineerNames = people.stream()
            .filter(p -> p.getDepartment().equals("Engineering")) // Filter
            .map(Person::getName)                                 // Transform
            .sorted()                                             // Sort
            .collect(Collectors.toList());                        // Collect
        
        System.out.println("Engineers: " + engineerNames);
        
        // 2. Complex pipeline with multiple operations
        Map<String, Double> averageAgePerDepartment = people.stream()
            .collect(Collectors.groupingBy(
                Person::getDepartment,
                Collectors.averagingInt(Person::getAge)
            ));
        
        System.out.println("Average age: " + averageAgePerDepartment);
        
        // 3. Reduce for aggregation
        int totalAge = people.stream()
            .mapToInt(Person::getAge)
            .reduce(0, Integer::sum); // Alternative: .sum()
        
        System.out.println("Total age: " + totalAge);
        
        // 4. Optional for safe processing
        Optional<Person> oldestPerson = people.stream()
            .max(Comparator.comparing(Person::getAge));
        
        oldestPerson.ifPresent(p -> 
            System.out.println("Oldest person: " + p.getName()));
        
        // 5. Custom collector
        String allNames = people.stream()
            .map(Person::getName)
            .collect(Collectors.joining(", "));
        
        System.out.println("All names: " + allNames);
        
        // 6. Parallel streams for performance
        List<Integer> largeNumbers = IntStream.range(1, 1_000_000)
            .boxed()
            .collect(Collectors.toList());
        
        long primeCount = largeNumbers.parallelStream()
            .filter(StreamAPIDemo::isPrime)
            .count();
        
        System.out.println("Prime count: " + primeCount);
    }
    
    // Pure function for prime checking
    private static boolean isPrime(int n) {
        if (n <= 1) return false;
        if (n <= 3) return true;
        if (n % 2 == 0 || n % 3 == 0) return false;
        
        for (int i = 5; i * i <= n; i += 6) {
            if (n % i == 0 || n % (i + 2) == 0) return false;
        }
        return true;
    }
}

// Person class for examples
class Person {
    private final String name;
    private final int age;
    private final String department;
    
    public Person(String name, int age, String department) {
        this.name = name;
        this.age = age;
        this.department = department;
    }
    
    public String getName() { return name; }
    public int getAge() { return age; }
    public String getDepartment() { return department; }
    
    @Override
    public String toString() {
        return name + " (" + age + ", " + department + ")";
    }
}

4. Higher-Order Functions and Closures

import java.util.function.*;
import java.util.*;

public class HigherOrderFunctionsDemo {
    
    // Higher-order function: takes a function as a parameter
    public static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
        List<R> result = new ArrayList<>();
        for (T element : list) {
            result.add(mapper.apply(element));
        }
        return result;
    }
    
    // Higher-order function: returns a function
    public static Function<Integer, Integer> multiplier(int factor) {
        return number -> number * factor; // Closure: factor is bound
    }
    
    // Higher-order function: returns a predicate
    public static Predicate<String> longerThan(int minLength) {
        return text -> text.length() > minLength;
    }
    
    // Higher-order function with multiple functions
    public static <T> List<T> processChain(List<T> list, 
                                           List<Function<T, T>> functions) {
        List<T> result = new ArrayList<>(list);
        
        for (Function<T, T> function : functions) {
            result = map(result, function);
        }
        
        return result;
    }
    
    // Currying (simplified)
    public static Function<Integer, Function<Integer, Integer>> addCurried() {
        return a -> b -> a + b;
    }
    
    // Function composition
    public static <T> Function<T, T> compose(Function<T, T> f, Function<T, T> g) {
        return x -> f.apply(g.apply(x));
    }
    
    public static void main(String[] args) {
        List<String> words = Arrays.asList("Java", "Python", "JavaScript", "C++");
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        // Using a higher-order function
        List<Integer> lengths = map(words, String::length);
        System.out.println("Lengths: " + lengths);
        
        // Return a function and use it
        Function<Integer, Integer> double_ = multiplier(2);
        Function<Integer, Integer> triple = multiplier(3);
        
        List<Integer> doubled = map(numbers, double_);
        List<Integer> tripled = map(numbers, triple);
        
        System.out.println("Doubled: " + doubled);
        System.out.println("Tripled: " + tripled);
        
        // Predicate higher-order function
        Predicate<String> longerThanThree = longerThan(3);
        List<String> longWords = words.stream()
            .filter(longerThanThree)
            .collect(Collectors.toList());
        
        System.out.println("Long words: " + longWords);
        
        // Function chain
        List<Function<Integer, Integer>> functions = Arrays.asList(
            n -> n * 2,    // double
            n -> n + 10,   // add
            n -> n / 3     // divide
        );
        
        List<Integer> processed = processChain(numbers, functions);
        System.out.println("Processed numbers: " + processed);
        
        // Currying
        Function<Integer, Function<Integer, Integer>> add = addCurried();
        Function<Integer, Integer> addFive = add.apply(5);
        int result = addFive.apply(3); // 5 + 3 = 8
        
        System.out.println("Currying result: " + result);
        
        // Function composition
        Function<Integer, Integer> square = n -> n * n;
        Function<Integer, Integer> increment = n -> n + 1;
        
        Function<Integer, Integer> squareThenIncrement = compose(increment, square);
        Function<Integer, Integer> incrementThenSquare = compose(square, increment);
        
        System.out.println("3²+1: " + squareThenIncrement.apply(3)); // 10
        System.out.println("(3+1)²: " + incrementThenSquare.apply(3)); // 16
    }
}

5. Functional Programming in Python

from typing import List, Callable, Optional
from functools import reduce
import operator

# Pure Functions
def addiere(a: int, b: int) -> int:
    return a + b

def filtere_gerade(zahlen: List[int]) -> List[int]:
    return [n for n in zahlen if n % 2 == 0]

def quadriere(zahlen: List[int]) -> List[int]:
    return [n * n for n in zahlen]

# Higher-Order Functions
def verarbeite(zahlen: List[int], operation: Callable[[int], int]) -> List[int]:
    return [operation(n) for n in zahlen]

def multiplizierer(faktor: int) -> Callable[[int], int]:
    return lambda x: x * faktor

# Function Composition
def komponiere(f: Callable, g: Callable) -> Callable:
    return lambda x: f(g(x))

# Currying
def addiere_curried(a: int):
    return lambda b: a + b

# Immutable Data Class
from dataclasses import dataclass

@dataclass(frozen=True)
class Person:
    name: str
    alter: int
    abteilung: str
    
    def mit_neuem_alter(self, neues_alter: int) -> 'Person':
        return Person(self.name, neues_alter, self.abteilung)

# Usage
def funktionale_demo():
    # Pure Functions
    zahlen = [1, 2, 3, 4, 5]
    gerade = filtere_gerade(zahlen)
    quadrate = quadriere(zahlen)
    
    print(f"Gerade: {gerade}")
    print(f"Quadrate: {quadrate}")
    
    # Higher-Order Functions
    verdoppeln = multiplizierer(2)
    verdreifachen = multiplizierer(3)
    
    verdoppelt = verarbeite(zahlen, verdoppeln)
    verdreifacht = verarbeite(zahlen, verdreifachen)
    
    print(f"Verdoppelt: {verdoppelt}")
    print(f"Verdreifacht: {verdreifacht}")
    
    # Function Composition
    quadrieren = lambda x: x * x
    inkrementieren = lambda x: x + 1
    
    quadrieren_dann_inkrementieren = komponiere(inkrementieren, quadrieren)
    inkrementieren_dann_quadrieren = komponiere(quadrieren, inkrementieren)
    
    print(f"3²+1: {quadrieren_dann_inkrementieren(3)}")  # 10
    print(f"(3+1)²: {inkrementieren_dann_quadrieren(3)}")  # 16
    
    # Currying
    addiere_fuenf = addiere_curried(5)
    ergebnis = addiere_fuenf(3)  # 8
    print(f"Currying Ergebnis: {ergebnis}")
    
    # Reduce for aggregation
    summe = reduce(operator.add, zahlen, 0)
    produkt = reduce(operator.mul, zahlen, 1)
    
    print(f"Summe: {summe}")
    print(f"Produkt: {produkt}")
    
    # Immutability
    alice = Person("Alice", 25, "Entwicklung")
    alice_aelter = alice.mit_neuem_alter(26)
    
    print(f"Original: {alice}")
    print(f"Verändert: {alice_aelter}")

if __name__ == "__main__":
    funktionale_demo()

Lambda Syntax Comparison

Java Lambda Expressions

// Various lambda forms
Predicate<String> leer = s -> s.isEmpty();
Predicate<String> leer2 = String::isEmpty; // Method Reference

Function<Integer, String> toString = i -> i.toString();
Function<Integer, String> toString2 = Object::toString;

Consumer<String> drucker = s -> System.out.println(s);
Consumer<String> drucker2 = System.out::println;

Supplier<Integer> zufall = () -> (int)(Math.random() * 100);

Python Lambda Expressions

# Lambda expressions
leer = lambda s: len(s) == 0
verdoppeln = lambda x: x * 2

# Higher-Order Functions with lambda
zahlen = [1, 2, 3, 4, 5]
verdoppelt = list(map(lambda x: x * 2, zahlen))
gerade = list(filter(lambda x: x % 2 == 0, zahlen))

JavaScript Lambda Expressions

// Arrow Functions
const leer = s => s.length === 0;
const verdoppeln = x => x * 2;

// Higher-Order Functions
const zahlen = [1, 2, 3, 4, 5];
const verdoppelt = zahlen.map(x => x * 2);
const gerade = zahlen.filter(x => x % 2 === 0);

Advantages and Disadvantages

Benefits of Functional Programming

  • Testability: Pure functions are straightforward to test
  • Parallelization: The absence of side effects enables safe concurrent processing
  • Reusability: Higher-order functions offer flexibility
  • Readability: Declarative code is often easier to understand
  • Fewer Bugs: Reducing state mutations minimizes defects

Drawbacks

  • Learning Curve: Functional thinking takes practice
  • Performance: Functional abstractions can introduce overhead
  • Memory: Immutability may consume additional memory
  • Debugging: Stack traces can become harder to follow

Common Interview Questions

  1. What’s the difference between a lambda expression and an anonymous class? A lambda expression is more concise syntax for a functional interface, while an anonymous class requires more boilerplate.

  2. Explain pure functions. Functions with no side effects that always return the same output for the same input.

  3. What is a higher-order function? A function that takes other functions as parameters or returns a function.

  4. Why is immutability important? It prevents unexpected state changes and makes parallelization safer and easier.

Key References

  1. https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
  2. https://docs.oracle.com/javase/tutorial/collections/streams/
  3. https://www.python.org/doc/essays/list2str.html

Keine Bücher für Kategorie "programming-languages" gefunden.

Back to Blog
Share:

Related Posts