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
- 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: 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
-
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.
-
Explain pure functions. Functions with no side effects that always return the same output for the same input.
-
What is a higher-order function? A function that takes other functions as parameters or returns a function.
-
Why is immutability important? It prevents unexpected state changes and makes parallelization safer and easier.
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.



