JavaRush /Java-Blog /Random-DE /Übersetzung des Buches. Funktionale Programmierung in Jav...
timurnav
Level 21

Übersetzung des Buches. Funktionale Programmierung in Java. Kapitel 1

Veröffentlicht in der Gruppe Random-DE
Ich helfe Ihnen gerne dabei, Fehler zu finden und die Qualität der Übersetzung zu verbessern. Ich übersetze, um meine Englischkenntnisse zu verbessern, und wenn Sie Übersetzungsfehler lesen und suchen, werden Sie sich noch besser verbessern als ich. Der Autor des Buches schreibt, dass das Buch viel Erfahrung im Umgang mit Java voraussetzt; ehrlich gesagt bin ich selbst nicht besonders erfahren, aber ich habe den Stoff im Buch verstanden. Das Buch befasst sich mit einer Theorie, die mit den Fingern schwer zu erklären ist. Wenn es im Wiki gute Artikel gibt, werde ich Links dazu bereitstellen, aber zum besseren Verständnis empfehle ich Ihnen, selbst zu googeln. Viel Glück an alle. :) Für diejenigen, die meine Übersetzung korrigieren möchten, sowie für diejenigen, denen sie zu dürftig zum Lesen auf Russisch ist, können Sie das Originalbuch hier herunterladen . Inhalt Kapitel 1 Hallo, Lambda-Ausdrücke – liest gerade Kapitel 2 Verwenden von Sammlungen – in der Entwicklung Kapitel 3 Zeichenfolgen, Komparatoren und Filter – in der Entwicklung Kapitel 4 Entwicklung mit Lambda-Ausdrücken – in der Entwicklung Kapitel 5 Arbeiten mit Ressourcen – in der Entwicklung Kapitel 6 Faul sein – in Entwicklung Kapitel 7 Ressourcen optimieren – in der Entwicklung Kapitel 8 Layout mit Lambda-Ausdrücken – in der Entwicklung Kapitel 9 Alles zusammenfügen – in der Entwicklung

Kapitel 1 Hallo, Lambda-Ausdrücke!

Unser Java-Code ist bereit für bemerkenswerte Transformationen. Die täglichen Aufgaben, die wir erledigen, werden einfacher, leichter und ausdrucksvoller. Die neue Art der Programmierung Java wird seit Jahrzehnten auch in anderen Sprachen verwendet. Mit diesen Änderungen an Java können wir prägnanten, eleganten und ausdrucksstarken Code mit weniger Fehlern schreiben. Damit können wir Standards einfach anwenden und gängige Designmuster mit weniger Codezeilen implementieren. In diesem Buch untersuchen wir den funktionalen Stil der Programmierung anhand einfacher Beispiele von Problemen, mit denen wir jeden Tag konfrontiert werden. Bevor wir uns mit diesem eleganten Stil und dieser neuen Art der Softwareentwicklung befassen, wollen wir sehen, warum sie besser ist.
Ändern Sie Ihr Denken
Der imperative Stil ist das, was uns Java seit der Einführung der Sprache gegeben hat. Dieser Stil legt nahe, dass wir Java jeden Schritt dessen beschreiben, was die Sprache tun soll, und dann einfach sicherstellen, dass diese Schritte genau befolgt werden. Das hat super funktioniert, aber das Niveau ist immer noch niedrig. Der Code war am Ende zu ausführlich und wir wollten oft eine etwas intelligentere Sprache. Wir könnten es dann deklarativ sagen – was wir wollen, und uns nicht damit befassen, wie es geht. Dank der Entwickler kann uns Java nun dabei helfen. Schauen wir uns einige Beispiele an, um die Vorteile und Unterschiede zwischen diesen Ansätzen zu verstehen.
Der übliche Weg
Beginnen wir mit den bekannten Grundlagen, um die beiden Paradigmen in Aktion zu sehen. Hierbei wird eine zwingende Methode verwendet, um in der Städtesammlung nach Chicago zu suchen – die Auflistungen in diesem Buch zeigen nur Codeausschnitte. boolean found = false; for(String city : cities) { if(city.equals("Chicago")) { found = true; break; } } System.out.println("Found chicago?:" + found); Die imperative Version des Codes ist laut (was hat dieses Wort damit zu tun?) und auf niedriger Ebene, es gibt mehrere veränderbare Teile. Zuerst erstellen wir dieses stinkende boolesche Flag namens „found“ und durchlaufen dann jedes Element in der Sammlung. Wenn wir die gesuchte Stadt finden, setzen wir das Flag auf true und unterbrechen die Schleife. Zum Schluss geben wir das Ergebnis unserer Suche auf der Konsole aus.
Es gibt einen besseren Weg
Als aufmerksamer Java-Programmierer kann ein kurzer Blick auf diesen Code ihn in etwas aussagekräftigeres und leichter lesbares verwandeln, etwa so: System.out.println("Found chicago?:" + cities.contains("Chicago")); Hier ist ein Beispiel für den deklarativen Stil – die Methode „contains()“ hilft uns, direkt zu dem zu gelangen, was wir brauchen.
Tatsächliche Änderungen
Diese Änderungen werden eine Menge Verbesserungen für unseren Code mit sich bringen:
  • Kein Aufwand mit veränderlichen Variablen
  • Schleifeniterationen sind unter der Haube verborgen
  • Weniger Code-Unordnung
  • Größere Codeklarheit, fokussierte Aufmerksamkeit
  • Weniger Impedanz; Der Code folgt eng der Geschäftsabsicht
  • Geringere Fehlerwahrscheinlichkeit
  • Leichter zu verstehen und zu unterstützen
Über einfache Fälle hinaus
Dies war ein einfaches Beispiel für eine deklarative Funktion, die das Vorhandensein eines Elements in einer Sammlung prüft; sie wird seit langem in Java verwendet. Stellen Sie sich nun vor, Sie müssten keinen zwingenden Code für komplexere Vorgänge wie das Parsen von Dateien, die Arbeit mit Datenbanken, das Stellen von Anfragen für Webdienste, das Erstellen von Multithreading usw. schreiben. Mit Java ist es jetzt möglich, prägnanten, eleganten Code zu schreiben, der Fehler nicht nur bei einfachen Vorgängen, sondern in der gesamten Anwendung erschwert.
Der alte Weg
Schauen wir uns ein anderes Beispiel an. Wir erstellen eine Sammlung mit Preisen und werden verschiedene Möglichkeiten ausprobieren, um die Summe aller reduzierten Preise zu berechnen. Angenommen, wir würden gebeten, alle Preise, deren Wert 20 $ übersteigt, mit einem Rabatt von 10 % zusammenzurechnen. Machen wir das zunächst auf die übliche Java-Art. Dieser Code sollte uns sehr bekannt sein: Zuerst erstellen wir eine veränderbare Variable totalOfDiscountedPrices , in der wir den resultierenden Wert speichern. Anschließend durchlaufen wir die Preissammlung, wählen Preise aus, die über 20 US-Dollar liegen, erhalten den rabattierten Preis und addieren diesen Wert zu totalOfDiscountedPrices . Am Ende zeigen wir die Summe aller Preise unter Berücksichtigung des Rabatts an. Unten sehen Sie, was an der Konsole ausgegeben wird final List prices = Arrays.asList( new BigDecimal("10"), new BigDecimal("30"), new BigDecimal("17"), new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("18"), new BigDecimal("45"), new BigDecimal("12")); BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO; for(BigDecimal price : prices) { if(price.compareTo(BigDecimal.valueOf(20)) > 0) totalOfDiscountedPrices = totalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9))); } System.out.println("Total of discounted prices: " + totalOfDiscountedPrices);
Summe der ermäßigten Preise: 67,5
Es funktioniert, aber der Code sieht chaotisch aus. Aber es ist nicht unsere Schuld, wir haben genutzt, was vorhanden war. Der Code hat ein ziemlich niedriges Niveau – er leidet unter einer Besessenheit von Grundelementen (googeln Sie ihn, interessantes Zeug) und steht im Widerspruch zum Prinzip der Einzelverantwortung . Diejenigen von uns, die zu Hause arbeiten, sollten solchen Code von den Augen von Kindern fernhalten, die Programmierer werden wollen. Er könnte ihren fragilen Geist beunruhigen. Seien Sie auf die Frage vorbereitet: „Müssen Sie das tun, um zu überleben?“
Es gibt einen besseren Weg, einen anderen
Jetzt können wir es besser machen, viel besser. Unser Code ähnelt möglicherweise einer Spezifikationsanforderung. Dies wird uns helfen, die Lücke zwischen Geschäftsanforderungen und dem Code, der sie implementiert, zu verringern und so die Wahrscheinlichkeit einer Fehlinterpretation von Anforderungen weiter zu verringern. Anstatt eine Variable zu erstellen und sie dann wiederholt zu ändern, arbeiten wir auf einer höheren Abstraktionsebene, wie in der folgenden Auflistung. final BigDecimal totalOfDiscountedPrices = prices.stream() .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9))) .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println("Total of discounted prices: " + totalOfDiscountedPrices); Lesen wir es laut vor: Der Preisfilter ist größer als 20. Ordnen Sie mithilfe des Schlüssels „Preis“ den Preis inklusive Rabatt zu (erstellen Sie „Schlüssel“-Wert-Paare) und fügen Sie sie dann hinzu
- Der Kommentar des Übersetzers bedeutet Wörter, die Ihnen beim Lesen des Codes im Kopf auftauchen.filter (price -> price.compareTo(BigDecimal.valueOf(20)) > 0)
Der Code wird gemeinsam in der gleichen logischen Reihenfolge ausgeführt, wie wir sie gelesen haben. Der Code wurde gekürzt, aber wir haben eine ganze Reihe neuer Dinge aus Java 8 verwendet. Zuerst haben wir die Methode stream() in der Preisliste aufgerufen . Dies öffnet die Tür zu einem benutzerdefinierten Iterator mit einer Vielzahl praktischer Funktionen, auf die wir später noch eingehen werden. Anstatt direkt alle Werte in der Preisliste zu durchlaufen , verwenden wir mehrere spezielle Methoden wie filter() und map() . Im Gegensatz zu den Methoden, die wir in Java und dem JDK verwendet haben, akzeptieren diese Methoden eine anonyme Funktion – einen Lambda-Ausdruck – als Parameter in Klammern. Wir werden es später genauer untersuchen. Durch Aufrufen der Methode „reduce()“ berechnen wir die Summe der in der Methode „map()“ erhaltenen Werte (ermäßigter Preis) . Die Schleife wird auf die gleiche Weise ausgeblendet wie bei Verwendung der Methode enthält() . Die Methoden filter() und map() sind jedoch noch komplexer. Für jeden Preis in der Preisliste rufen sie die übergebene Lambda-Funktion auf und speichern sie in einer neuen Sammlung. Die Methode „reduce()“ wird für diese Sammlung aufgerufen, um das Endergebnis zu erzeugen. Unten sehen Sie, was an der Konsole ausgegeben wird
Summe der ermäßigten Preise: 67,5
Änderungen
Nachfolgend finden Sie Änderungen gegenüber der üblichen Methode:
  • Der Code ist optisch ansprechend und nicht überladen.
  • Keine Operationen auf niedriger Ebene
  • Einfacher, die Logik zu verbessern oder zu ändern
  • Die Iteration wird durch eine Methodenbibliothek gesteuert
  • Effiziente Lazy- Loop- Auswertung
  • Bei Bedarf einfacher zu parallelisieren
Wir werden später besprechen, wie Java diese Verbesserungen bietet.
Lambda zur Rettung :)
Lambda ist der funktionale Schlüssel, um uns von den Problemen der zwingenden Programmierung zu befreien. Indem wir die Art und Weise, wie wir programmieren, mit den neuesten Funktionen von Java ändern, können wir Code schreiben, der nicht nur elegant und prägnant ist, sondern auch weniger fehleranfällig, effizienter und einfacher zu optimieren, zu verbessern und multithreadfähig zu machen.
Gewinnen Sie große Gewinne durch funktionale Programmierung
Der funktionale Programmierstil hat ein höheres Signal-Rausch-Verhältnis ; Wir schreiben weniger Codezeilen, aber jede Zeile oder jeder Ausdruck führt mehr Funktionalität aus. Wir haben aus der funktionalen Version des Codes im Vergleich zum Imperativ wenig gewonnen:
  • Wir haben unerwünschte Änderungen oder Neuzuweisungen von Variablen vermieden, die eine Fehlerquelle darstellen und die gleichzeitige Verarbeitung von Code aus verschiedenen Threads erschweren. In der Imperativversion legen wir in der gesamten Schleife unterschiedliche Werte für die Variable totalOfDiscountedPrices fest . In der Funktionsversion gibt es keine explizite Änderung der Variablen im Code. Weniger Änderungen führen zu weniger Fehlern im Code.
  • Die funktionale Version des Codes lässt sich einfacher parallelisieren. Selbst wenn die Berechnungen in der Methode „map()“ langwierig wären, können wir sie ohne Angst parallel ausführen. Wenn wir aus verschiedenen Threads auf Code im Imperativ-Stil zugreifen, müssen wir uns gleichzeitig um die Änderung der Variablen „totalOfDiscountedPrices“ kümmern . In der Funktionsversion greifen wir erst auf die Variable zu, nachdem alle Änderungen vorgenommen wurden. Dies befreit uns von der Sorge um die Thread-Sicherheit des Codes.
  • Der Code ist ausdrucksvoller. Anstatt den Code in mehreren Schritten auszuführen – Erstellen und Initialisieren einer Variablen mit einem Dummy-Wert, Durchlaufen der Preisliste, Hinzufügen von Rabattpreisen zur Variablen usw. – bitten wir einfach die Methode „map()“ der Liste, eine andere Liste zurückzugeben von ermäßigten Preisen und addieren Sie diese .
  • Der funktionale Stil ist prägnanter: Es sind weniger Codezeilen erforderlich als bei der imperativen Version. Kompakterer Code bedeutet, dass weniger geschrieben, weniger gelesen und einfacher gewartet werden muss.
  • Die funktionale Version des Codes ist intuitiv und leicht zu verstehen, sobald Sie die Syntax kennen. Die Methode map() wendet die übergebene Funktion (die den reduzierten Preis berechnet) auf jedes Element der Sammlung an und erstellt eine Sammlung mit dem Ergebnis, wie wir im Bild unten sehen können.

Bild Abbildung 1 – Die Map-Methode wendet die übergebene Funktion auf jedes Element der Sammlung an
Mit der Unterstützung von Lambda-Ausdrücken können wir die Leistungsfähigkeit des funktionalen Programmierstils in Java voll ausschöpfen. Wenn wir diesen Stil beherrschen, können wir ausdrucksstärkeren, prägnanteren Code mit weniger Änderungen und Fehlern erstellen. Einer der Hauptvorteile von Java war bisher die Unterstützung des objektorientierten Paradigmas. Und der funktionale Stil steht nicht im Widerspruch zu OOP. Echte Exzellenz beim Übergang von der imperativen zur deklarativen Programmierung. Mit Java 8 können wir funktionale Programmierung sehr effektiv mit einem objektorientierten Stil kombinieren. Wir können den OO-Stil weiterhin auf Objekte, ihren Umfang, ihren Zustand und ihre Beziehungen anwenden. Darüber hinaus können wir Verhalten und Änderungszustand, Geschäftsprozesse und Datenverarbeitung als eine Reihe von Funktionssätzen modellieren.
Warum im funktionalen Stil programmieren?
Wir haben die allgemeinen Vorteile des funktionalen Programmierstils gesehen, aber lohnt es sich, diesen neuen Stil zu erlernen? Wird dies eine geringfügige Änderung in der Sprache sein oder wird es unser Leben verändern? Wir müssen Antworten auf diese Fragen bekommen, bevor wir unsere Zeit und Energie verschwenden. Das Schreiben von Java-Code ist nicht so schwierig; die Syntax der Sprache ist einfach. Wir sind mit vertrauten Bibliotheken und APIs vertraut. Was uns wirklich die Mühe abverlangt, Code zu schreiben und zu warten, sind die typischen Unternehmensanwendungen, bei denen wir Java für die Entwicklung verwenden. Wir müssen sicherstellen, dass andere Programmierer die Verbindungen zur Datenbank zum richtigen Zeitpunkt schließen, dass sie sie nicht länger als nötig halten oder Transaktionen ausführen, dass sie Ausnahmen vollständig und auf der richtigen Ebene abfangen, dass sie Sperren ordnungsgemäß anwenden und freigeben ... dieses Blatt lässt sich noch sehr lange fortsetzen. Jedes der oben genannten Argumente allein hat kein Gewicht, aber zusammen mit der inhärenten Implementierungskomplexität wird es überwältigend, zeitaufwändig und schwierig umzusetzen. Was wäre, wenn wir diese Komplexitäten in winzige Codeteile kapseln könnten, die sie auch gut bewältigen könnten? Dann würden wir nicht ständig Energie für die Umsetzung von Standards aufwenden. Dies würde einen erheblichen Vorteil bringen. Schauen wir uns also an, wie ein funktionaler Stil helfen kann.
fragt Joe
Bedeutet ein kurzer* Code einfach weniger Codebuchstaben?
* Wir sprechen über das Wort prägnant , das den funktionalen Stil von Code unter Verwendung von Lambda-Ausdrücken charakterisiert
In diesem Zusammenhang soll der Code prägnant, ohne Schnörkel und auf direkte Wirkung reduziert sein, um die Absicht effektiver zu vermitteln. Das sind weitreichende Vorteile. Das Schreiben von Code ist wie das Zusammenfügen von Zutaten: Ihn prägnant zu gestalten ist, als würde man ihm Soße hinzufügen. Manchmal ist das Schreiben eines solchen Codes mit mehr Aufwand verbunden. Es muss weniger Code gelesen werden, aber es macht den Code transparenter. Beim Kürzen ist es wichtig, den Code klar zu halten. Prägnanter Code ähnelt Designtricks. Dieser Code erfordert weniger Tanzen mit einem Tamburin. Das bedeutet, dass wir unsere Ideen schnell umsetzen und weitermachen können, wenn sie funktionieren, und sie aufgeben können, wenn sie nicht den Erwartungen entsprechen.
Iterationen zu Steroiden
Wir verwenden Iteratoren, um Objektlisten zu verarbeiten und mit Mengen und Karten zu arbeiten. Die Iteratoren, die wir in Java verwenden, sind uns vertraut; obwohl sie primitiv sind, sind sie nicht einfach. Sie beanspruchen nicht nur mehrere Codezeilen, sondern sind auch ziemlich schwierig zu schreiben. Wie durchlaufen wir alle Elemente einer Sammlung? Wir könnten eine for-Schleife verwenden. Wie wählen wir einige Elemente aus der Sammlung aus? Verwendung derselben for-Schleife, aber Verwendung einiger zusätzlicher veränderlicher Variablen, die mit etwas aus der Sammlung verglichen werden müssen. Wie führen wir dann, nachdem wir einen bestimmten Wert ausgewählt haben, Operationen an einem einzelnen Wert durch, z. B. einem Minimum, einem Maximum oder einem Durchschnittswert? Wieder Zyklen, wieder neue Variablen. Das erinnert an das Sprichwort „Man sieht die Bäume wegen des Waldes nicht“ (das Original verwendet ein Wortspiel im Zusammenhang mit Iterationen und bedeutet „Alles wird in Angriff genommen, aber nicht alles gelingt“ – Anmerkung des Übersetzers). jdk bietet jetzt interne Iteratoren für verschiedene Anweisungen: einen zum Vereinfachen von Schleifen, einen zum Binden der erforderlichen Ergebnisabhängigkeiten, einen zum Filtern der Ausgabewerte, einen zum Zurückgeben der Werte und mehrere praktische Funktionen zum Abrufen von Min., Max., Durchschnittswerten usw. Darüber hinaus lässt sich die Funktionalität dieser Vorgänge sehr einfach kombinieren, sodass wir verschiedene Sätze davon kombinieren können, um Geschäftslogik einfacher und mit weniger Code zu implementieren. Wenn wir fertig sind, ist der Code leichter zu verstehen, da er eine logische Lösung in der für das Problem erforderlichen Reihenfolge erstellt. Wir werden uns einige Beispiele für solchen Code in Kapitel 2 und später in diesem Buch ansehen.
Anwendung von Algorithmen
Algorithmen treiben Unternehmensanwendungen voran. Beispielsweise müssen wir einen Vorgang bereitstellen, der eine Autoritätsprüfung erfordert. Wir müssen sicherstellen, dass die Transaktionen schnell abgeschlossen werden und die Kontrollen korrekt durchgeführt werden. Solche Aufgaben werden oft auf eine ganz gewöhnliche Methode reduziert, wie in der folgenden Auflistung: Transaction transaction = getFromTransactionFactory(); //... Операция выполняющаяся во время транзакции... checkProgressAndCommitOrRollbackTransaction(); UpdateAuditTrail(); Bei diesem Ansatz gibt es zwei Probleme. Erstens führt dies häufig zu einer Verdoppelung des Entwicklungsaufwands, was wiederum zu einem Anstieg der Kosten für die Wartung der Anwendung führt. Zweitens ist es sehr leicht, Ausnahmen zu übersehen, die im Code dieser Anwendung ausgelöst werden können, wodurch die Ausführung der Transaktion und die Weitergabe von Schecks gefährdet werden. Wir können einen richtigen Try-finally-Block verwenden, aber jedes Mal, wenn jemand diesen Code berührt, müssen wir erneut überprüfen, ob die Logik des Codes nicht beschädigt wurde. Andernfalls könnten wir die Fabrik verlassen und den gesamten Code auf den Kopf stellen. Anstatt Transaktionen zu empfangen, könnten wir Verarbeitungscode an eine gut verwaltete Funktion senden, wie zum Beispiel den folgenden Code. runWithinTransaction((Transaction transaction) -> { //... Операция выполняющаяся во время транзакции... }); Diese kleinen Änderungen summieren sich zu enormen Einsparungen. Der Algorithmus zur Statusprüfung und Anwendungsprüfung erhält eine neue Abstraktionsebene und wird mit der Methode runWithinTransaction() gekapselt . In dieser Methode platzieren wir einen Code, der im Kontext einer Transaktion ausgeführt werden soll. Wir müssen uns keine Sorgen mehr machen, dass wir etwas vergessen haben oder ob wir die Ausnahme an der richtigen Stelle entdeckt haben. Dafür sorgen algorithmische Funktionen. Auf dieses Thema wird in Kapitel 5 ausführlicher eingegangen.
Algorithmuserweiterungen
Algorithmen werden immer häufiger eingesetzt, doch damit sie in der Entwicklung von Unternehmensanwendungen vollständig genutzt werden können, sind Möglichkeiten zu ihrer Erweiterung erforderlich.
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION