Functional Programming: Lambda Expressions & Functional Interfaces
This guide provides a comprehensive overview of functional programming — covering lambda expressions, functional interfaces, and higher-order functions with practical examples.
In a Nutshell
Functional programming emphasizes 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 type is inferred from the target type 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)— transform values - Consumer:
void accept(T t)— consume values - Supplier:
T get()— produce values
Key Principles:
- Pure Functions: Immutable input → deterministic output
- Immutability: Data cannot be changed
- Referential Transparency: A function call can be replaced with its result
- Higher-Order Functions: Functions as parameters or return values
Essential Topics
- Lambda Expressions: Anonymous functions with compact syntax
- Functional Interfaces: Exactly one abstract method
- Higher-Order Functions: Functions as parameters and return values
- Pure Functions: No side effects, deterministic behavior
- Immutability: Unchangeable data structures
- Streams API: Declarative data processing
- Method References: Concise references to methods
- Modern Java: Functional programming patterns
Key Components
- Lambda Expressions:
(x, y) -> x + y - Functional Interfaces:
Predicate<T>,Function<T,R> - Pure Functions: No side effects
- Immutability: Unchangeable objects
- Higher-Order Functions:
map(),filter(),reduce() - Streams: Sequential data processing
- Method References:
String::length - Closures: Access to outer 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;
// Combine 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 modifications (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 only (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 the 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("Modified: " + 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> averageAgeByDepartment = people.stream()
.collect(Collectors.groupingBy(
Person::getDepartment,
Collectors.averagingInt(Person::getAge)
));
System.out.println("Average age: " + averageAgeByDepartment);
// 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 function as 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 function
public static Function<Integer, Integer> multiplier(int factor) {
return number -> number * factor; // Closure: factor is bound
}
// Higher-order function: returns 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 higher-order function
List<Integer> lengths = map(words, String::length);
System.out.println("Lengths: " + lengths);
// Return function and use it
Function<Integer, Integer> double_it = multiplier(2);
Function<Integer, Integer> triple_it = multiplier(3);
List<Integer> doubled = map(numbers, double_it);
List<Integer> tripled = map(numbers, triple_it);
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 Across Languages
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 Arrow Functions
// 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);
Strengths and Weaknesses
Advantages of Functional Programming
- Testability: Pure functions are straightforward to test
- Parallelization: Absence of side effects enables safe concurrent processing
- Reusability: Higher-order functions are highly flexible
- Readability: Declarative code is often more intuitive
- Reliability: Fewer bugs stemming from state mutations
Disadvantages
- Learning curve: Functional thinking requires practice and adjustment
- Performance: Functional abstractions may introduce overhead
- Memory: Immutability can consume more memory
- Debugging: Stack traces can become harder to interpret
Common Interview Questions
-
What’s the difference between a lambda expression and an anonymous class? Lambda expressions provide more concise syntax for functional interfaces, whereas anonymous classes require boilerplate code.
-
Explain pure functions. Functions without side effects that always return the same output for the same input.
-
What is a higher-order function? A function that accepts other functions as parameters or returns a function.
-
Why is immutability important? It prevents unexpected state changes and simplifies concurrent programming.
Key References
- https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
- https://docs.oracle.com/javase/tutorial/collections/streams/
- https://www.python.org/doc/essays/list2str.html
Recommended Reading
Keine Bücher für Kategorie "programming-languages" gefunden.



