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

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: 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.

Buchempfehlung

Programmiersprachen

Bücher über Python, JavaScript, Rust, Go und mehr

Python 3: Das umfassende Handbuch

Python 3: Das umfassende Handbuch

Bei Amazon ansehen

Affiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.

Java ist auch eine Insel: Einführung, Ausbildung, Praxis

Java ist auch eine Insel: Einführung, Ausbildung, Praxis

Bei Amazon ansehen

Affiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.

Clean Code - Refactoring, Patterns, Testen und Techniken für sauberen Code: Deutsche Ausgabe

Clean Code - Refactoring, Patterns, Testen und Techniken für sauberen Code: Deutsche Ausgabe

Bei Amazon ansehen

Affiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.

Clean Code für Dummies: Besser programmieren. Professionelle Softwareentwicklung.

Clean Code für Dummies: Besser programmieren. Professionelle Softwareentwicklung.

Bei Amazon ansehen

Affiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.

Das Komplette Docker Handbuch: Containerisierte Anwendungen Erstellen, Verteilen, Ausführen

Das Komplette Docker Handbuch: Containerisierte Anwendungen Erstellen, Verteilen, Ausführen

Bei Amazon ansehen

Affiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.

Handbuch für Softwareentwickler: Das Standardwerk für professionelles Software Engineering

Handbuch für Softwareentwickler: Das Standardwerk für professionelles Software Engineering

Bei Amazon ansehen

Affiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.

Weitere Funktionale Programmierung Artikel

Funktionale Programmierung ist ein wichtiges Paradigma der modernen Softwareentwicklung. Die folgenden Artikel helfen dir, alle Aspekte der funktionalen Programmierung zu meistern.

Grundlagen und Konzepte

Zurück zum Blog
Share:

Ähnliche Beiträge