Funktionale Programmierung: Lambda, Functional Interfaces & Higher-Order Functions
Funktionale Programmierung ist ein Programmierparadigma, das Funktionen als primäre Bausteine behandelt. Es fördert unveränderliche Daten und reine Funktionen für bessere Testbarkeit und Parallelisierbarkeit.
Grundlagen der Funktionalen Programmierung
Kernprinzipien
- Pure Functions: Funktionen ohne Seiteneffekte
- Immutability: Unveränderliche Datenstrukturen
- First-Class Functions: Funktionen als Werte
- Higher-Order Functions: Funktionen, die andere Funktionen als Parameter nehmen
- Function Composition: Kombination von Funktionen
Reine vs Unreine Funktionen
public class FunctionExamples {
// Reine Funktion - keine Seiteneffekte
public static int add(int a, int b) {
return a + b;
// Immer gleiches Ergebnis für gleiche Eingaben
// Keine Seiteneffekte
}
// Unreine Funktion - mit Seiteneffekten
private static int counter = 0;
public static int addToCounter(int value) {
counter += value; // Seiteneffekt: ändert globalen Zustand
return counter;
// Verschiedene Ergebnisse für gleiche Eingaben
}
// Reine Funktion für String-Verarbeitung
public static String capitalize(String input) {
if (input == null) return null;
return input.toUpperCase();
}
// Unreine Funktion mit externer Abhängigkeit
public static String getCurrentTimeFormatted() {
return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now());
// Hängt von aktueller Zeit ab
}
}
Lambda Ausdrücke in Java
Syntax und Grundlagen
public class LambdaBasics {
// Traditionelle anonyme Klasse
public static void traditionalApproach() {
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("Traditional approach");
}
};
runnable1.run();
}
// Lambda Ausdruck
public static void lambdaApproach() {
Runnable runnable2 = () -> System.out.println("Lambda approach");
runnable2.run();
}
// Lambda mit verschiedenen Syntax-Varianten
public static void lambdaSyntaxExamples() {
// Keine Parameter
Runnable noParams = () -> System.out.println("No parameters");
// Ein Parameter
Consumer<String> oneParam = s -> System.out.println("Parameter: " + s);
// Ein Parameter mit Typ
Consumer<String> oneParamTyped = (String s) -> System.out.println("Typed: " + s);
// Mehrere Parameter
BinaryOperator<Integer> twoParams = (a, b) -> a + b;
// Mehrere Parameter mit Typen
BinaryOperator<Integer> twoParamsTyped = (Integer a, Integer b) -> a + b;
// Mehrere Zeilen
Predicate<String> multiLine = s -> {
String trimmed = s.trim();
return trimmed.length() > 5 && trimmed.startsWith("A");
};
// Verwendung
noParams.run();
oneParam.accept("Hello");
System.out.println(twoParams.apply(5, 3));
System.out.println(multiLine.test("Hello World"));
}
}
Functional Interfaces
// Eigene Functional Interfaces
@FunctionalInterface
public interface StringProcessor {
String process(String input);
// Kann default Methoden haben
default String processAndLog(String input) {
String result = process(input);
System.out.println("Processed: " + result);
return result;
}
// Kann static Methoden haben
static StringProcessor toUpperCase() {
return String::toUpperCase;
}
}
@FunctionalInterface
public interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
// Verwendung von Functional Interfaces
public class FunctionalInterfaceExamples {
public static void demonstrateInterfaces() {
// Predicate - Boolean Test
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<String> isLong = s -> s.length() > 10;
System.out.println("4 is even: " + isEven.test(4));
System.out.println("Hello is long: " + isLong.test("Hello"));
// Consumer - Konsumiert Werte
Consumer<String> printer = System.out::println;
Consumer<List<Integer>> listPrinter = list -> list.forEach(System.out::println);
printer.accept("Hello World");
listPrinter.accept(Arrays.asList(1, 2, 3, 4, 5));
// Supplier - Liefert Werte
Supplier<Double> randomSupplier = Math::random;
Supplier<LocalDateTime> nowSupplier = LocalDateTime::now;
System.out.println("Random: " + randomSupplier.get());
System.out.println("Now: " + nowSupplier.get());
// Function - Transformation
Function<String, Integer> stringLength = String::length;
Function<Integer, String> intToString = Object::toString;
System.out.println("Length of Hello: " + stringLength.apply("Hello"));
System.out.println("String of 42: " + intToString.apply(42));
// UnaryOperator - Spezielle Function
UnaryOperator<String> upperCase = String::toUpperCase;
UnaryOperator<Integer> square = n -> n * n;
System.out.println("Upper: " + upperCase.apply("hello"));
System.out.println("Square: " + square.apply(5));
// BinaryOperator - Zwei Parameter
BinaryOperator<Integer> add = Integer::sum;
BinaryOperator<String> concat = String::concat;
System.out.println("Add: " + add.apply(3, 7));
System.out.println("Concat: " + concat.apply("Hello", " World"));
// Eigenes Functional Interface
StringProcessor reverser = s -> new StringBuilder(s).reverse().toString();
StringProcessor prefixAdder = s -> "Prefix: " + s;
System.out.println("Reverse: " + reverser.process("Hello"));
System.out.println("Prefix: " + prefixAdder.process("World"));
// Custom TriFunction
TriFunction<Integer, Integer, Integer, Integer> sumThree = (a, b, c) -> a + b + c;
System.out.println("Sum of 1,2,3: " + sumThree.apply(1, 2, 3));
}
}
Higher-Order Functions
Funktionen als Parameter
public class HigherOrderFunctions {
// Funktion, die eine Funktion als Parameter nimmt
public static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
List<R> result = new ArrayList<>();
for (T item : list) {
result.add(mapper.apply(item));
}
return result;
}
// Funktion, die eine Funktion als Parameter nimmt und filtert
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T item : list) {
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
// Funktion, die eine Funktion für Reduktion nimmt
public static <T> T reduce(List<T> list, T identity, BinaryOperator<T> accumulator) {
T result = identity;
for (T item : list) {
result = accumulator.apply(result, item);
}
return result;
}
// Funktion, die eine Funktion zurückgibt (Closure)
public static Function<Integer, Integer> createMultiplier(int multiplier) {
return number -> number * multiplier;
}
// Funktion mit mehreren Funktionen als Parameter
public static <T> List<T> processList(
List<T> list,
Predicate<T> filter,
Function<T, T> mapper,
Consumer<T> consumer) {
List<T> result = new ArrayList<>();
for (T item : list) {
if (filter.test(item)) {
T processed = mapper.apply(item);
consumer.accept(processed);
result.add(processed);
}
}
return result;
}
// Demonstration
public static void demonstrateHigherOrderFunctions() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Map: Zahlen quadrieren
List<Integer> squares = map(numbers, n -> n * n);
System.out.println("Squares: " + squares);
// Filter: Nur gerade Zahlen
List<Integer> evens = filter(numbers, n -> n % 2 == 0);
System.out.println("Evens: " + evens);
// Reduce: Summe berechnen
Integer sum = reduce(numbers, 0, Integer::sum);
System.out.println("Sum: " + sum);
// Closure: Multiplier-Funktion erstellen
Function<Integer, Integer> doubler = createMultiplier(2);
Function<Integer, Integer> tripler = createMultiplier(3);
System.out.println("Double of 5: " + doubler.apply(5));
System.out.println("Triple of 5: " + tripler.apply(5));
// Komplexe Verarbeitung
List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
List<String> processed = processList(
words,
s -> s.length() > 5, // Filter: nur lange Wörter
String::toUpperCase, // Map: in Großbuchstaben
System.out::println // Consumer: ausgeben
);
System.out.println("Processed: " + processed);
}
}
Function Composition
public class FunctionComposition {
// Function Composition Hilfsmethoden
public static <T, U, R> Function<T, R> compose(Function<U, R> f, Function<T, U> g) {
return x -> f.apply(g.apply(x));
}
public static <T> Function<T, T> compose(Function<T, T>... functions) {
return Arrays.stream(functions)
.reduce(Function.identity(), Function::andThen);
}
// Beispiel für Composition
public static void demonstrateComposition() {
// Einzelne Funktionen
Function<String, String> addPrefix = s -> "Hello " + s;
Function<String, String> addSuffix = s -> s + "!";
Function<String, String> toUpperCase = String::toUpperCase;
// Funktionen kombinieren
Function<String, String> greet = compose(addSuffix, addPrefix);
Function<String, String> greetLoud = compose(toUpperCase, greet);
System.out.println(greet.apply("World")); // Hello World!
System.out.println(greetLoud.apply("World")); // HELLO WORLD!
// Mit andThen (reihenfolge umgekehrt)
Function<String, String> greetAndThenUpper = addPrefix.andThen(toUpperCase).andThen(addSuffix);
System.out.println(greetAndThenUpper.apply("World")); // HELLO WORLD!
// Mathematische Beispiele
Function<Double, Double> multiplyBy2 = x -> x * 2;
Function<Double, Double> add3 = x -> x + 3;
Function<Double, Double> square = x -> x * x;
// (x * 2 + 3)²
Function<Double, Double> complexOperation = compose(square, compose(add3, multiplyBy2));
System.out.println("Complex operation (5): " + complexOperation.apply(5.0)); // ((5*2)+3)² = 13² = 169
}
}
Stream API und Funktionale Programmierung
Stream Processing
public class StreamFunctionalProgramming {
public static void demonstrateStreamProcessing() {
List<Person> people = Arrays.asList(
new Person("Alice", 25, "Engineering"),
new Person("Bob", 30, "Marketing"),
new Person("Charlie", 35, "Engineering"),
new Person("Diana", 28, "HR"),
new Person("Eve", 32, "Engineering")
);
// Komplexe Stream-Verarbeitung
List<String> result = people.stream()
.filter(p -> p.getAge() >= 30) // Filter: Alter >= 30
.filter(p -> p.getDepartment().equals("Engineering")) // Filter: Engineering
.map(Person::getName) // Map: nur Namen
.map(String::toUpperCase) // Map: Großbuchstaben
.sorted() // Sortieren
.collect(Collectors.toList()); // Sammeln
System.out.println("Filtered and mapped: " + result);
// Reduktion mit spezialisierten Operatoren
double totalAge = people.stream()
.mapToInt(Person::getAge)
.sum();
System.out.println("Total age: " + totalAge);
// Gruppierung
Map<String, List<Person>> byDepartment = people.stream()
.collect(Collectors.groupingBy(Person::getDepartment));
System.out.println("By department: " + byDepartment);
// Partitionierung
Map<Boolean, List<Person>> byAge = people.stream()
.collect(Collectors.partitioningBy(p -> p.getAge() >= 30));
System.out.println("By age >= 30: " + byAge);
// Custom Collector
Map<String, Double> avgAgeByDept = people.stream()
.collect(Collectors.groupingBy(
Person::getDepartment,
Collectors.averagingInt(Person::getAge)
));
System.out.println("Average age by department: " + avgAgeByDept);
}
// Person Klasse für Beispiele
public static class Person {
private String name;
private int age;
private String department;
public Person(String name, int age, String department) {
this.name = name;
this.age = age;
this.department = department;
}
// Getter
public String getName() { return name; }
public int getAge() { return age; }
public String getDepartment() { return department; }
@Override
public String toString() {
return String.format("%s (%d, %s)", name, age, department);
}
}
}
Lazy Evaluation mit Streams
public class LazyEvaluation {
public static void demonstrateLazyEvaluation() {
// Unendlicher Stream - funktioniert durch Lazy Evaluation
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);
// Nur erste 10 Elemente werden berechnet
List<Integer> firstTen = infiniteStream
.limit(10)
.collect(Collectors.toList());
System.out.println("First 10: " + firstTen);
// Fibonacci Zahlen mit Lazy Evaluation
Stream<Long> fibonacci = Stream.generate(new FibonacciSupplier());
List<Long> firstFibonacci = fibonacci
.limit(10)
.collect(Collectors.toList());
System.out.println("First 10 Fibonacci: " + firstFibonacci);
// Lazy Filter und Map
List<Integer> processed = Stream.iterate(1, n -> n + 1)
.filter(LazyEvaluation::isPrime) // Nur Primzahlen
.map(n -> n * n) // Quadrieren
.limit(5) // Nur erste 5
.collect(Collectors.toList());
System.out.println("First 5 prime squares: " + processed);
}
private static boolean isPrime(int n) {
if (n <= 1) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) return false;
}
return true;
}
// Fibonacci Supplier
private static class FibonacciSupplier implements Supplier<Long> {
private long a = 0;
private long b = 1;
@Override
public Long get() {
long result = a;
long next = a + b;
a = b;
b = next;
return result;
}
}
}
Funktionale Programmierung in Python
Lambda und Higher-Order Functions
from functools import reduce, partial
from typing import List, Callable, Any
# Lambda Ausdrücke
def lambda_examples():
# Einfache Lambda-Funktionen
square = lambda x: x ** 2
add = lambda x, y: x + y
is_even = lambda x: x % 2 == 0
print(f"Square of 5: {square(5)}")
print(f"Add 3 + 7: {add(3, 7)}")
print(f"Is 4 even: {is_even(4)}")
# Lambda mit eingebauten Funktionen
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Map mit Lambda
squares = list(map(lambda x: x ** 2, numbers))
print(f"Squares: {squares}")
# Filter mit Lambda
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(f"Evens: {evens}")
# Reduce mit Lambda
sum_all = reduce(lambda x, y: x + y, numbers)
print(f"Sum: {sum_all}")
# Sortieren mit Lambda
words = ["apple", "banana", "cherry", "date"]
sorted_by_length = sorted(words, key=lambda x: len(x))
print(f"Sorted by length: {sorted_by_length}")
# Higher-Order Functions
def higher_order_functions():
# Funktion, die Funktion als Parameter nimmt
def apply_operation(numbers: List[int], operation: Callable[[int], int]) -> List[int]:
return [operation(num) for num in numbers]
# Funktion, die Funktion zurückgibt
def create_multiplier(factor: int) -> Callable[[int], int]:
return lambda x: x * factor
# Verwendung
numbers = [1, 2, 3, 4, 5]
squares = apply_operation(numbers, lambda x: x ** 2)
cubes = apply_operation(numbers, lambda x: x ** 3)
print(f"Squares: {squares}")
print(f"Cubes: {cubes}")
# Closure
doubler = create_multiplier(2)
tripler = create_multiplier(3)
print(f"Double of 5: {doubler(5)}")
print(f"Triple of 5: {tripler(5)}")
# Function Composition
def compose(f: Callable, g: Callable) -> Callable:
return lambda x: f(g(x))
add_one = lambda x: x + 1
multiply_by_two = lambda x: x * 2
add_then_multiply = compose(multiply_by_two, add_one)
multiply_then_add = compose(add_one, multiply_by_two)
print(f"Add then multiply (5): {add_then_multiply(5)}") # (5 + 1) * 2 = 12
print(f"Multiply then add (5): {multiply_then_add(5)}") # (5 * 2) + 1 = 11
# Partial Functions
def partial_functions():
def multiply(x: int, y: int) -> int:
return x * y
def power(base: int, exponent: int) -> int:
return base ** exponent
# Partial application
double = partial(multiply, 2)
triple = partial(multiply, 3)
square = partial(power, 2)
cube = partial(power, 3)
print(f"Double of 5: {double(5)}")
print(f"Triple of 5: {triple(5)}")
print(f"Square of 5: {square(5)}")
print(f"Cube of 5: {cube(5)}")
# List Comprehensions (funktionale Alternative)
def list_comprehensions():
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Traditionell mit map/filter
squares_even = list(filter(lambda x: x % 2 == 0, map(lambda x: x ** 2, numbers)))
# Mit List Comprehension
squares_even_comp = [x ** 2 for x in numbers if x % 2 == 0]
print(f"Squares of evens (map/filter): {squares_even}")
print(f"Squares of evens (comprehension): {squares_even_comp}")
# Nested comprehensions
matrix = [[i * j for j in range(1, 4)] for i in range(1, 4)]
print(f"Multiplication table: {matrix}")
# Dictionary comprehension
word_lengths = {word: len(word) for word in ["apple", "banana", "cherry"]}
print(f"Word lengths: {word_lengths}")
# Decorators (Higher-Order Functions)
def decorators():
def timing_decorator(func):
import time
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
def memoization_decorator(func):
cache = {}
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@timing_decorator
@memoization_decorator
def fibonacci(n: int) -> int:
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(f"Fibonacci(10): {fibonacci(10)}")
print(f"Fibonacci(15): {fibonacci(15)}") # Schneller durch Memoization
# Alle Beispiele ausführen
if __name__ == "__main__":
print("=== Lambda Examples ===")
lambda_examples()
print("\n=== Higher-Order Functions ===")
higher_order_functions()
print("\n=== Partial Functions ===")
partial_functions()
print("\n=== List Comprehensions ===")
list_comprehensions()
print("\n=== Decorators ===")
decorators()
Immutability und Pure Functions
Unveränderliche Datenstrukturen
public class ImmutableDataStructures {
// Unveränderliche Person Klasse
public static final class Person {
private final String name;
private final int age;
private final List<String> hobbies;
public Person(String name, int age, List<String> hobbies) {
this.name = Objects.requireNonNull(name);
this.age = age;
this.hobbies = List.copyOf(hobbies); // Defensive Copy
}
// Getter - keine Setter!
public String getName() { return name; }
public int getAge() { return age; }
public List<String> getHobbies() { return hobbies; }
// Methoden erstellen neue Instanzen
public Person withAge(int newAge) {
return new Person(this.name, newAge, this.hobbies);
}
public Person withName(String newName) {
return new Person(newName, this.age, this.hobbies);
}
public Person addHobby(String hobby) {
List<String> newHobbies = new ArrayList<>(this.hobbies);
newHobbies.add(hobby);
return new Person(this.name, this.age, newHobbies);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name) &&
Objects.equals(hobbies, person.hobbies);
}
@Override
public int hashCode() {
return Objects.hash(name, age, hobbies);
}
@Override
public String toString() {
return String.format("Person{name='%s', age=%d, hobbies=%s}", name, age, hobbies);
}
}
// Funktionale Operationen auf unveränderlichen Daten
public static void demonstrateImmutability() {
List<Person> people = Arrays.asList(
new Person("Alice", 25, Arrays.asList("reading", "swimming")),
new Person("Bob", 30, Arrays.asList("gaming", "cooking")),
new Person("Charlie", 35, Arrays.asList("music", "travel"))
);
// Alte Liste bleibt unverändert
List<Person> olderPeople = people.stream()
.map(person -> person.withAge(person.getAge() + 1))
.collect(Collectors.toList());
System.out.println("Original: " + people);
System.out.println("Aged by 1: " + olderPeople);
// Funktionale Transformation
List<String> hobbies = people.stream()
.flatMap(person -> person.getHobbies().stream())
.distinct()
.sorted()
.collect(Collectors.toList());
System.out.println("All hobbies: " + hobbies);
}
}
Pure Function Beispiele
public class PureFunctions {
// Reine Funktion - keine Seiteneffekte
public static int calculateArea(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Dimensions must be positive");
}
return width * height;
}
// Reine Funktion für String-Verarbeitung
public static String formatFullName(String firstName, String lastName) {
if (firstName == null || lastName == null) {
return "";
}
return String.format("%s %s",
firstName.trim().toUpperCase(),
lastName.trim().toUpperCase()
);
}
// Reine Funktion für Listenverarbeitung
public static List<Integer> filterAndSquare(List<Integer> numbers, Predicate<Integer> predicate) {
return numbers.stream()
.filter(predicate)
.map(n -> n * n)
.collect(Collectors.toList());
}
// Unreine Funktion - zum Vergleich
private static int counter = 0;
public static int incrementCounter() {
return counter++; // Seiteneffekt!
}
// Demonstration
public static void demonstratePureFunctions() {
// Pure Function - immer gleiches Ergebnis
int area1 = calculateArea(5, 3);
int area2 = calculateArea(5, 3);
System.out.println("Areas are equal: " + (area1 == area2)); // true
// Pure Function mit Strings
String fullName1 = formatFullName("John", "Doe");
String fullName2 = formatFullName("John", "Doe");
System.out.println("Names are equal: " + fullName1.equals(fullName2)); // true
// Pure Function mit Listen
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenSquares = filterAndSquare(numbers, n -> n % 2 == 0);
System.out.println("Even squares: " + evenSquares);
// Unreine Funktion - verschiedene Ergebnisse
int count1 = incrementCounter();
int count2 = incrementCounter();
System.out.println("Counts are different: " + (count1 != count2)); // true
}
}
Monaden und Funktionale Konzepte
Optional Monad
public class MonadExamples {
// Optional für null-sichere Operationen
public static void demonstrateOptional() {
Optional<String> optional = Optional.of("Hello World");
// Map Transformation
Optional<Integer> length = optional.map(String::length);
System.out.println("Length: " + length.orElse(0));
// Filter
Optional<String> filtered = optional.filter(s -> s.length() > 5);
System.out.println("Filtered: " + filtered.orElse("Too short"));
// FlatMap für verschachtelte Optionals
Optional<String> upperCase = optional.flatMap(s ->
s.length() > 5 ? Optional.of(s.toUpperCase()) : Optional.empty()
);
System.out.println("Upper case: " + upperCase.orElse("Not available"));
// Null-sichere Kette
String result = Optional.ofNullable(getUser())
.map(User::getAddress)
.map(Address::getCity)
.orElse("Unknown");
System.out.println("City: " + result);
}
// Either Monad (vereinfachte Implementierung)
public static class Either<L, R> {
private final L left;
private final R right;
private Either(L left, R right) {
this.left = left;
this.right = right;
}
public static <L, R> Either<L, R> left(L value) {
return new Either<>(value, null);
}
public static <L, R> Either<L, R> right(R value) {
return new Either<>(null, value);
}
public boolean isLeft() { return left != null; }
public boolean isRight() { return right != null; }
public L getLeft() { return left; }
public R getRight() { return right; }
public <T> Either<L, T> map(Function<R, T> mapper) {
if (isRight()) {
return Either.right(mapper.apply(right));
}
return Either.left(left);
}
public <T> Either<L, T> flatMap(Function<R, Either<L, T>> mapper) {
if (isRight()) {
return mapper.apply(right);
}
return Either.left(left);
}
}
// Either Verwendung
public static Either<String, Integer> parseNumber(String input) {
try {
return Either.right(Integer.parseInt(input));
} catch (NumberFormatException e) {
return Either.left("Invalid number: " + input);
}
}
public static void demonstrateEither() {
Either<String, Integer> result1 = parseNumber("123");
Either<String, Integer> result2 = parseNumber("abc");
result1.map(n -> n * 2)
.map(Object::toString)
.ifRight(System.out::println)
.ifLeft(System.err::println);
result2.map(n -> n * 2)
.map(Object::toString)
.ifRight(System.out::println)
.ifLeft(System.err::println);
}
// Hilfsmethoden für Either
private static <T> void ifRight(Either<String, T> either, Consumer<T> consumer) {
if (either.isRight()) {
consumer.accept(either.getRight());
}
}
private static <T> void ifLeft(Either<String, T> either, Consumer<String> consumer) {
if (either.isLeft()) {
consumer.accept(either.getLeft());
}
}
// Dummy Klassen für Beispiele
private static User getUser() {
return new User("John", new Address("123 Main St", "New York"));
}
private static class User {
private String name;
private Address address;
public User(String name, Address address) {
this.name = name;
this.address = address;
}
public Address getAddress() { return address; }
}
private static class Address {
private String street;
private String city;
public Address(String street, String city) {
this.street = street;
this.city = city;
}
public String getCity() { return city; }
}
}
Performance und Best Practices
Funktionale Programmierung Performance
public class FunctionalPerformance {
// Traditionelle Schleife
public static int traditionalSum(List<Integer> numbers) {
int sum = 0;
for (int num : numbers) {
sum += num;
}
return sum;
}
// Funktionale Variante mit Stream
public static int functionalSum(List<Integer> numbers) {
return numbers.stream().reduce(0, Integer::sum);
}
// Parallel Stream für große Datenmengen
public static int parallelSum(List<Integer> numbers) {
return numbers.parallelStream().reduce(0, Integer::sum);
}
// Performance Vergleich
public static void performanceComparison() {
List<Integer> numbers = IntStream.range(0, 1_000_000)
.boxed()
.collect(Collectors.toList());
// Warm-up
traditionalSum(numbers);
functionalSum(numbers);
parallelSum(numbers);
// Benchmark
long start = System.nanoTime();
int result1 = traditionalSum(numbers);
long traditionalTime = System.nanoTime() - start;
start = System.nanoTime();
int result2 = functionalSum(numbers);
long functionalTime = System.nanoTime() - start;
start = System.nanoTime();
int result3 = parallelSum(numbers);
long parallelTime = System.nanoTime() - start;
System.out.println("Results equal: " + (result1 == result2 && result2 == result3));
System.out.println("Traditional: " + (traditionalTime / 1_000_000) + " ms");
System.out.println("Functional: " + (functionalTime / 1_000_000) + " ms");
System.out.println("Parallel: " + (parallelTime / 1_000_000) + " ms");
}
// Best Practices
public static void bestPractices() {
List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
// 1. Method References statt Lambda wenn möglich
List<Integer> lengths1 = words.stream()
.map(s -> s.length()) // Lambda
.collect(Collectors.toList());
List<Integer> lengths2 = words.stream()
.map(String::length) // Method Reference
.collect(Collectors.toList());
// 2. Primitive Streams für Performance
int sum = words.stream()
.mapToInt(String::length) // IntStream statt Stream<Integer>
.sum();
// 3. Lazy Evaluation nutzen
Optional<String> firstLong = words.stream()
.filter(s -> s.length() > 5)
.findFirst(); // Stoppt nach erstem Treffer
// 4. Unveränderliche Operationen bevorzugen
List<String> processed = words.stream()
.map(String::toUpperCase)
.filter(s -> s.startsWith("A"))
.collect(Collectors.toList()); // Neue Liste statt modification
System.out.println("Best practices completed");
}
}
Prüfungsrelevante Konzepte
Wichtige funktionale Konzepte
- Pure Functions: Keine Seiteneffekte, deterministisch
- Immutability: Unveränderliche Datenstrukturen
- Higher-Order Functions: Funktionen als Parameter/Rückgabewerte
- Function Composition: Kombination von Funktionen
- Lazy Evaluation: Verzögerte Auswertung
- Monads: Optional, Either, Stream
Typische Prüfungsaufgaben
- Implementieren Sie eine pure Funktion
- Erklären Sie Function Composition
- Vergleichen Sie imperative vs funktionale Ansätze
- Implementieren Sie einen Higher-Order Function
- Beschreiben Sie Vorteile der funktionalen Programmierung
Zusammenfassung
Funktionale Programmierung bietet viele Vorteile:
- Testbarkeit: Reine Funktionen sind leicht zu testen
- Parallelisierbarkeit: Keine Seiteneffekte erleichtern nebenläufige Programmierung
- Wartbarkeit: Unveränderliche Daten reduzieren Fehler
- Lesbarkeit: Deklarativer Code ist oft klarer
Die Kombination von objektorientierter und funktionaler Programmierung ermöglicht robuste, skalierbare und wartbare Softwarearchitekturen.