CodeGym /Kurse /JAVA 25 SELF /Funktionskomposition: compose, andThen

Funktionskomposition: compose, andThen

JAVA 25 SELF
Level 49, Lektion 1
Verfügbar

1. Begriff der Funktionskomposition

Ein wenig Theorie (aber nicht zu trocken)

In der Mathematik ist Funktionskomposition, wenn das Ergebnis einer Funktion als Eingabe der nächsten dient. Haben wir die Funktionen f und g, bedeutet die Komposition g(f(x)): zuerst wenden wir f auf x an, dann setzen wir das Ergebnis in g ein.

In der Programmierung ist die Idee dieselbe: Wir möchten komplexe Transformationen aus einfachen Bausteinen zusammensetzen, anstatt eine riesige Funktion für alles zu schreiben. Das macht den Code flexibel, wiederverwendbar und gut lesbar.

Stellen Sie sich ein Törtchen-Fließband vor: zuerst der Teig (f), dann die Creme (g), dann die Streusel (h). Der gesamte Prozess ist h(g(f(Zutaten))).

Warum ist Komposition wichtig?

  • Komposition erlaubt es, ein Programm aus kleinen Funktions-„Bausteinen“ zusammenzusetzen.
  • Wiederverwendung: Einen Baustein kann man an verschiedenen Stellen einsetzen, ohne zu duplizieren.
  • Flexibilität: Um einen Schritt zu ändern, ersetzen wir die entsprechende Funktion – der Rest bleibt unberührt.
  • Lesbarkeit und Testbarkeit: Kleine Funktionen sind einfacher zu lesen, zu prüfen und zu warten.

2. Die Methoden compose und andThen im Interface Function

Interface Function: kurze Erinnerung

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    // Methoden für die Komposition:
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before)
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after)
}
  • compose: Zuerst wird die Funktion ausgeführt, die Sie an compose übergeben, danach die aktuelle.
  • andThen: Zuerst wird die aktuelle Funktion ausgeführt, danach die an andThen übergebene.

Visuelles Schema

// Angenommen, es gibt zwei Funktionen:
Function<String, Integer> parse = s -> Integer.parseInt(s);
Function<Integer, Integer> square = x -> x * x;

// compose: square.compose(parse) == x -> square.apply(parse.apply(x))
"5" --parse--> 5 --square--> 25

// andThen: parse.andThen(square) == x -> square.apply(parse.apply(x))
"5" --parse--> 5 --square--> 25

// Aber die Reihenfolge ist wichtig, wenn die Typen unterschiedlich sind!

Beispiel: String in Zahl und dann zum Quadrat

import java.util.function.Function;

public class ComposeAndThenDemo {
    public static void main(String[] args) {
        // Funktion: wandelt String in Zahl um
        Function<String, Integer> parse = s -> Integer.parseInt(s);

        // Funktion: quadriert eine Zahl
        Function<Integer, Integer> square = x -> x * x;

        // Kombinieren: erst parsen, dann quadrieren
        Function<String, Integer> parseThenSquare = parse.andThen(square);

        System.out.println(parseThenSquare.apply("7")); // 49

        // Und wenn wir vertauschen?
        // square.compose(parse) — dasselbe Ergebnis (für diese Funktionen)
        Function<String, Integer> squareOfParsed = square.compose(parse);

        System.out.println(squareOfParsed.apply("8")); // 64
    }
}

Wann spielt die Reihenfolge eine Rolle?

Wenn die Typen der Funktionen nicht zusammenpassen, wird die Reihenfolge kritisch. Zum Beispiel:

Function<String, String> addPrefix = s -> "User: " + s;
Function<String, Integer> length = s -> s.length();

Function<String, Integer> composed = addPrefix.andThen(length);
System.out.println(composed.apply("Alice")); // "User: Alice" -> 11

// Und so:
// length.andThen(addPrefix) — Kompilierfehler!
// length liefert Integer, aber addPrefix erwartet String.

Tabelle: Unterschied zwischen compose und andThen

f
g
f.compose(g)
f.andThen(g)
T → R
V → T
V → R: f(g(x))
T → V: g(f(x))

3. Komposition von Prädikaten und anderen Interfaces

Predicate<T>: and, or, negate

Das funktionale Interface Predicate<T> ist eine Funktion, die boolean zurückgibt. Zum Kombinieren von Prädikaten gibt es spezielle Methoden:

  • and: logisches UND (&&)
  • or: logisches ODER (||)
  • negate: logisches NICHT (!)

Beispiel: komplexe Filterbedingungen

Angenommen, wir haben eine Benutzerklasse:

public class User {
    String name;
    int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

Nun schreiben wir verschiedene Prädikate:

import java.util.function.Predicate;

Predicate<User> isAdult = user -> user.age >= 18;
Predicate<User> nameStartsWithA = user -> user.name.startsWith("A");

// Kombinieren: volljährig und Name beginnt mit "A"
Predicate<User> adultAndA = isAdult.and(nameStartsWithA);

// Volljährig oder Name beginnt mit "A"
Predicate<User> adultOrA = isAdult.or(nameStartsWithA);

// Nicht volljährig
Predicate<User> notAdult = isAdult.negate();

Diese Prädikate können wir jetzt beim Filtern verwenden, z. B. mit dem Stream-API:

import java.util.List;
import java.util.stream.Collectors;

List<User> users = List.of(
    new User("Alice", 20),
    new User("Bob", 17),
    new User("Anna", 15),
    new User("Mike", 22)
);

List<User> filtered = users.stream()
    .filter(adultAndA)
    .collect(Collectors.toList());

// In filtered landet nur Alice (volljährig und Name beginnt mit "A")

Komposition von Consumer, Function, Supplier

  • Consumer<T>: die Methode andThen ermöglicht es, zwei Operationen hintereinander auszuführen.
  • Function<T, R>: die Komposition haben wir oben betrachtet.
  • Supplier<T>: lässt sich nicht direkt kombinieren, kann aber innerhalb anderer Funktionen verwendet werden.

Beispiel: Consumer<T>.andThen

import java.util.function.Consumer;

Consumer<String> print = s -> System.out.println("Erhalten: " + s);
Consumer<String> printUpper = s -> System.out.println("In Großbuchstaben: " + s.toUpperCase());

Consumer<String> combined = print.andThen(printUpper);

combined.accept("hello");
// Ausgabe:
// Erhalten: hello
// In Großbuchstaben: HELLO

4. Praxis: Transformations- und Filterketten

Aufgabe 1: Eine Transformationskette Function aufbauen

Angenommen, in unserer Anwendung speichern wir Benutzer als Strings "Name,Alter", zum Beispiel "Alice,20". Es soll:

  • Den String in ein User-Objekt umwandeln
  • Das Alter ermitteln
  • Prüfen, ob der Benutzer volljährig ist
import java.util.function.Function;
import java.util.function.Predicate;

Function<String, User> stringToUser = str -> {
    String[] parts = str.split(",");
    return new User(parts[0], Integer.parseInt(parts[1]));
};
Function<User, Integer> getAge = user -> user.age;
Predicate<Integer> isAdultAge = age -> age >= 18;

// Kombinieren: String -> User -> Alter -> Prädikat
Function<String, Integer> stringToAge = stringToUser.andThen(getAge);

String input = "Bob,19";
int age = stringToAge.apply(input);
System.out.println("Alter: " + age); // 19

System.out.println("Volljährig? " + isAdultAge.test(age)); // true

Aufgabe 2: Mehrere Predicate für das Filtern kombinieren

Wir möchten Benutzer auswählen, die älter als 18 sind und deren Name mit "A" oder "M" beginnt.

Predicate<User> isAdult = user -> user.age > 18;
Predicate<User> nameStartsWithA = user -> user.name.startsWith("A");
Predicate<User> nameStartsWithM = user -> user.name.startsWith("M");

Predicate<User> filter = isAdult.and(nameStartsWithA.or(nameStartsWithM));

List<User> filtered = users.stream()
    .filter(filter)
    .collect(Collectors.toList());

Aufgabe 3: Mehrstufige Transformation Function

Es soll: einen String nehmen, trimmen, in Großbuchstaben umwandeln, den Präfix "USER: " hinzufügen.

Function<String, String> trim = String::trim;
Function<String, String> toUpper = String::toUpperCase;
Function<String, String> addPrefix = s -> "USER: " + s;

// Kette zusammenstellen
Function<String, String> pipeline = trim.andThen(toUpper).andThen(addPrefix);

System.out.println(pipeline.apply("   john   ")); // USER: VASYA

5. Typische Fehler bei der Funktionskomposition

Fehler Nr. 1: Vertauschte Reihenfolge compose/andThen.
Anfänger verwechseln oft, was zuerst und was danach ausgeführt wird. Merksatz: f.compose(g) — zuerst g, dann f; f.andThen(g) — zuerst f, dann g.

Fehler Nr. 2: Nicht übereinstimmende Typen.
Wenn der Ergebnis-Typ der einen Funktion nicht dem Parameter-Typ der anderen entspricht, lässt der Compiler die Kombination nicht zu. Zum Beispiel kann man nicht Function<Integer, String>.andThen(Function<Double, Boolean>) machen.

Fehler Nr. 3: Zu komplexe Ketten.
Manchmal versucht man, die gesamte Business-Logik in eine einzige Kette zu pressen und erhält Spaghetti-Code. Zerlegen Sie in kleinere Funktionen und geben Sie ihnen aussagekräftige Namen.

Fehler Nr. 4: Nebeneffekte in Funktionen.
Funktionen und Prädikate sollten möglichst „rein“ sein (ohne Nebeneffekte), sonst wird die Komposition riskant und unvorhersehbar.

1
Aufgabe
JAVA 25 SELF, Level 49, Lektion 1
Gesperrt
Standardisierung von Logins für die Geheimgesellschaft 🕵️‍♂️
Standardisierung von Logins für die Geheimgesellschaft 🕵️‍♂️
1
Aufgabe
JAVA 25 SELF, Level 49, Lektion 1
Gesperrt
Magische Zahlentransformationen an der Zauberschule 🧙‍♀️
Magische Zahlentransformationen an der Zauberschule 🧙‍♀️
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION