Interessante Logik

Der Vergleich von imperativen und funktionalen Algorithmen in Java 8

Lukas Eder

©Shutterstock/Sathitanont N

Lukas Eder ist begeistert vom funktionalen Programmieren in Java 8, von dem er glaubt, dass es die expressive Power des deklarativen SQL-Programmierens haben wird. In diesem Artikel beschreibt er die imperativen und funktionalen Ansätze, die Java 8 bietet.

Ein populärer Tweet von Mario Fusco zeigt eindrucksvoll, worin der Hauptunterschied zwischen imperativen und funktionalen Ansätzen im Vergleich zu ähnlichen Algorithmen besteht:

Beide Algorithmen tun das selbe, wahrscheinlich sind sie ähnlich schnell und sinnvoll. Und trotzdem ist einer davon viel einfacher zu schreiben – und auch zu lesen – als der andere. Der Unterschied besteht in dem Fakt, dass beim imperativen Programmieren verschiedene algorithmische Anforderungen im Code-Block zu finden sind, wohingegen beim funktionalen Programmieren jede Anforderung eine eigenen Codezeile erhält. Vergleichen Sie selbst:

  • Grün: Error handling
  • Blau: Stop criteria
  • Rot: IO operations
  • Gelb: “Business logic”

Funktionales Programmieren ist nicht immer besser als imperatives Programmieren, wie andere Beispiele auf dem jOOQ Blog zeigen:

Doch auf Stack Overflow findet sich ein Beispiel der Userin Aurora_Toitanium, bei dem der Unterschied wie im oben genannten Beispiel von Mario Fusco sehr deutlich wird:

Calculating the Duplicate Values in an Array

Die Idee ist, die Summe aller Werte zu bilden, die in einer Reihe von Werten doppelt vorkommen. Beispielsweise sollte das folgende Array …

int[] list = new int[]{1,2,3,4,5,6,7,8,8,8,9,10};

… dieses Ergebnis liefern:

Duplicate: 8. Sum of all duplicate values: 24

Der imperative Ansatz

Der User Volkan Ozkan wählt in seinem Antwort-Post einen imperativen Ansatz und berechnet die Summe folgendermaßen:

int[] array = new int[] {
    1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 9, 10
};

int sum = 0;
for (int j = 0; j < array.length; j++)
{
    for (int k = j + 1; k < array.length; k++)
    {
        if (k != j && array[k] == array[j])
        {
            sum = sum + array[k];
            System.out.println(
                "Duplicate found: "
              + array[k]
              + " "
              + "Sum of the duplicate value is " + sum);
        }
    }
}

Dieser Ansatz funktioniert nur mit sortierten Arrays, in denen Duplikate direkt hintereinander erscheinen. In diesem Fall ist es, im Hinblick auf die Performance, jedoch wahrscheinlich die beste Lösung – falls Performance in diesem Algorithmus eine Rolle spielen soll.

Der funktionale Ansatz

Wenn ein kleiner Performanceverlust für Sie in Ordnung ist (was vermutlich zutrifft), so können Sie den oberen, etwas schwer zu lesenden Code mit dem folgenden Stück funktionaler Java-8-Logik, in der viel deutlicher wird, was gemacht wird, ersetzen:

int[] array = new int[] {
    1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 9, 10
};

IntStream.of(array)
         .boxed()
         .collect(groupingBy(i -> i))
         .entrySet()
         .stream()
         .filter(e -> e.getValue().size() > 1)
         .forEach(e -> {
             System.out.println(
                 "Duplicates found for : "
               + e.getKey()
               + " their sum being : "
               + e.getValue()
                  .stream()
                  .collect(summingInt(i -> i)));
         });

Oder, mit Erklärungen:

int[] array = new int[] {
    1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 9, 10
};

// Create a Stream<Integer> from your data
IntStream.of(array)
         .boxed()

// Group values into a Map<Integer, List<Integer>>
         .collect(groupingBy(i -> i))

// Filter out those map values that have only
// 1 element in their group
         .entrySet()
         .stream()
         .filter(e -> e.getValue().size() > 1)

// Print the sum for the remaining groups
         .forEach(e -> {
             System.out.println(
                 "Duplicates found for : "
               + e.getKey()
               + " their sum being : "
               + e.getValue()
                  .stream()
                  .collect(summingInt(i -> i)));
         });

 

(Anmerkung: Der funktionale Ansatz berechnet die Summe für jeden doppelten Wert, und nicht, wie der imperative Ansatz, die Gesamtsumme. Dies könnte mit der initialen Frage noch nicht klar gewesen sein. Von der ursprünglichen Fragestellung her war diese Anforderung nicht ganz klar.)

Wie wir in früheren Artikeln auf dem jOOQ-Blog dargelegt haben, liegt die Power des funktionalen Programmierens mit Hilfe einer API wie der Java 8 Stream API darin, dass wir uns der expressiven Power des deklarativen SQL-Programmierens annähern. Wir müssen uns nicht länger individuelle Array-Indizes merken; wie man sie berechnet und wie man Zwischenergebnisse in irgendwelchen Puffern abspeichert. Wir können uns nun auf die wirklich interessante Logik konzentrieren, wie: „Was ist ein Duplikat?“ oder „An welcher Summe bin ich interessiert?“

Über den Vergleich von SQL und Java 8 Streams können Sie hier mehr lesen.

Aufmacherbild: BANGKOK, THAILAND- APRIL 25, 2015: Rubik’s cube on orange background, Rubik’s cube invented by a Hungarian architect Erno Rubik in 1974. von Shutterstock / Urheberrecht: Sathitanont N

Geschrieben von
Lukas Eder
Lukas Eder
Lukas Eder ist leidenschaftlicher Java- und SQL-Entwickler. Er ist Gründer und Leiter der Forschungs- und Entwicklungsabteilung der Data Geekery GmbH, dem Unternehmen hinter jOOQ - die einfachste Art, um SQL in Java zu schreiben.
Kommentare

Hinterlasse einen Kommentar

2 Kommentare auf "Der Vergleich von imperativen und funktionalen Algorithmen in Java 8"

avatar
400
  Subscribe  
Benachrichtige mich zu:
P. Huber
Gast
Warum am Ende noch mal streamen? e.getValue() .stream() .collect(summingInt(i -> i)) Kann man nicht einfacher sagen: e.getValue().size() * e.getKey() Und da zeigt sich das Problem – Das Streamingbeispiel ist nämlich NICHT wirklich einfacher, denn es passiert hier ziemlich viel implizit. Im Beispiel mit den Schleifen sieht man nämlich die Ergebnisstruktur explizit…sonst wäre der Fehler auch nicht passiert, richtig? Nicht falsch verstehen, ich finde das neue Streaming API auch toll, es ist aber nicht die Lösung für alles.