Skip to content
IRC-Coding IRC-Coding
Functional Programming Lambda Functional Interfaces Higher-Order Functions Java 8 Stream API Pure Functions Immutability

Funktionale Programmierung: Lambda, Functional Interfaces & Higher-Order Functions

Funktionale Programmierung mit Lambda Ausdrücken, Functional Interfaces und Higher-Order Functions. Java 8+ und Python Beispiele.

S

schutzgeist

2 min read

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

  1. Pure Functions: Keine Seiteneffekte, deterministisch
  2. Immutability: Unveränderliche Datenstrukturen
  3. Higher-Order Functions: Funktionen als Parameter/Rückgabewerte
  4. Function Composition: Kombination von Funktionen
  5. Lazy Evaluation: Verzögerte Auswertung
  6. Monads: Optional, Either, Stream

Typische Prüfungsaufgaben

  1. Implementieren Sie eine pure Funktion
  2. Erklären Sie Function Composition
  3. Vergleichen Sie imperative vs funktionale Ansätze
  4. Implementieren Sie einen Higher-Order Function
  5. 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.

Zurück zum Blog
Share:

Ähnliche Beiträge