Das Buch "Modernes Java. Lambda-Ausdrücke, Streams und funktionale Programmierung “

Bild Hallo habrozhiteli! Der Vorteil moderner Anwendungen liegt in fortschrittlichen Lösungen, einschließlich Microservices, reaktiven Architekturen und Daten-Streaming. Lambda-Ausdrücke, Datenströme und das lang erwartete Modulsystem der Java-Plattform vereinfachen deren Implementierung erheblich.

Das Buch hilft Ihnen dabei, neue Funktionen moderner Add-Ons wie die Streams-API und das Java-Plattformmodulsystem kennenzulernen. Entdecken Sie neue Ansätze zur Wettbewerbsfähigkeit und erfahren Sie, wie Funktionskonzepte die Arbeit mit Code verbessern.

In diesem Buch: • Neue Java-Funktionen • Streaming-Daten und reaktive Programmierung • Java-Plattformmodulsystem.

Auszug. Kapitel 11. Optionale Klasse als beste Alternative zu null


Erhöhen Sie Ihre Hand, wenn Sie während Ihrer Karriere als Java-Entwickler jemals eine NullPointerException erhalten haben. Lassen Sie es ausgelöst, wenn diese Ausnahme die häufigste ist, auf die Sie gestoßen sind. Leider sehen wir Sie jetzt nicht, aber es ist sehr wahrscheinlich, dass Ihre Hand erhoben wird. Wir vermuten auch, dass Sie etwas denken wie: „Ja, ich stimme zu. Ausnahmen NullPointerException bereitet jedem Java-Entwickler Kopfschmerzen, egal ob Anfänger oder Experte. Aber mit ihnen kann sowieso nichts gemacht werden. Dies ist der Preis, den wir für die Verwendung eines so praktischen und wahrscheinlich unvermeidlichen Designs als leere Links zahlen. “ Dies ist eine verbreitete Meinung in der Welt der (imperativen) Programmierung; vielleicht ist dies jedoch nicht die ganze Wahrheit, sondern ein tief verwurzeltes Vorurteil.

Der britische Informatikspezialist Tony Hoare, der 1965 Null-Links erstellte, während er die ALGOL W-Sprache entwickelte, eine der ersten typisierten Programmiersprachen mit Datensätzen, für die Speicher auf dem Haufen zugewiesen wurde, gab später zu, dass er dies tat. nur wegen der einfachen Implementierung. “ Obwohl er "die volle Sicherheit der Verwendung einer Ausnahme für leere Links" gewährleisten wollte, hielt er dies für die bequemste Methode, um das Fehlen von Wert zu simulieren. Viele Jahre später bereute er diese Entscheidung und nannte sie "meinen Milliarden-Dollar-Fehler". Wir alle haben die Ergebnisse dieser Entscheidung gesehen. Zum Beispiel können wir das Feld eines Objekts überprüfen, um festzustellen, ob es einen von zwei möglichen Werten darstellt, nur um festzustellen, dass wir kein Objekt, sondern einen Nullzeiger prüfen, und sofort diese nervige NullPointerException erhalten.

Tatsächlich hat Hoar möglicherweise die Kosten für die Behebung von Millionen von Entwicklern von Fehlern unterschätzt, die in den letzten 50 Jahren durch leere Links verursacht wurden. Tatsächlich basiert die überwiegende Mehrheit der in den letzten Jahrzehnten erstellten Programmiersprachen1, einschließlich Java, auf derselben Entwurfsentscheidung, möglicherweise aus Gründen der Kompatibilität mit älteren Sprachen oder (wahrscheinlicher), wie Hoar sagte, „einfach aufgrund der einfachen Implementierung ". Wir beginnen mit der Demonstration eines einfachen Beispiels für die Probleme, die bei der Verwendung von null auftreten.

11.1. Wie man einen Wertmangel simuliert


Stellen Sie sich vor, Sie haben die verschachtelte Objektstruktur in Listing 11.1 für den Besitzer eines Autos, das eine Autoversicherung gekauft hat.

Listing 11.1. Datenmodell für Person / Auto / Versicherung

public class Person { private Car car; public Car getCar() { return car; } } public class Car { private Insurance insurance; public Insurance getInsurance() { return insurance; } } public class Insurance { private String name; public String getName() { return name; } } 

Was ist Ihrer Meinung nach das Problem mit dem folgenden Code?

 public String getCarInsuranceName(Person person) { return person.getCar().getInsurance().getName(); } 

Dieser Code sieht ziemlich vernünftig aus, aber viele Leute haben kein Auto. Was ist in diesem Fall das Ergebnis des Aufrufs der getCar-Methode? Oft (und vergebens) geben sie einen leeren Link zurück, um das Fehlen eines Werts anzuzeigen (in diesem Fall, um das Fehlen einer Maschine anzuzeigen). Infolgedessen gibt der Aufruf der Methode getInsurance eine leere Linkversicherung zurück, wodurch zur Laufzeit eine NullPointerException ausgelöst und das Programm gestoppt wird. Das ist aber noch nicht alles. Aber was ist, wenn das Personenobjekt null war? Was ist, wenn getInsurance auch null zurückgibt?

11.1.1. Reduzieren Sie die NullPointerException mit der Sicherheitsüberprüfung


Wie vermeide ich unerwartete NullPointerException? Normalerweise können Sie Nullprüfungen überall dort hinzufügen, wo Sie sie benötigen (und manchmal die Anforderungen einer sicheren Programmierung überschreiten und wo Sie dies nicht müssen), und dies häufig in verschiedenen Stilen. Unser erster Versuch, eine Methode zu schreiben, um die Generierung einer NullPointerException zu verhindern, ist in Listing 11.2 dargestellt.

Bild

Diese Methode prüft jedes Mal, wenn eine Variable dereferenziert wird, auf null und gibt den Zeichenfolgenwert "Unbekannt" zurück, wenn mindestens eine der Variablen in dieser Dereferenzierungskette ein leerer Wert ist. Die einzige Ausnahme von dieser Regel ist, dass wir den Namen der Versicherungsgesellschaft nicht auf null prüfen, da wir wissen, dass sie (wie jede andere Gesellschaft) einen Namen haben muss. Bitte beachten Sie, dass wir diese letzte Überprüfung nur aufgrund der Kenntnis des Themenbereichs vermeiden können. Diese Tatsache spiegelt sich jedoch nicht in Java-Klassen wider, die unsere Daten modellieren.

Wir haben die Methode in Listing 11.2 als „tiefe Zweifel“ beschrieben, da sich darin ein sich wiederholendes Muster bemerkbar macht: Jedes Mal, wenn die Variable im Zweifelsfall nicht null ist, müssen Sie einen weiteren verschachtelten if-Block hinzufügen, wodurch die Ebene der Codeeinrückung erhöht wird. Offensichtlich lässt sich diese Technik nicht gut skalieren und verringert die Lesbarkeit. Daher ist es besser, eine andere Lösung auszuprobieren. Um dieses Problem zu vermeiden, gehen wir einen anderen Weg, wie in Listing 11.3 gezeigt.

Im zweiten Versuch versuchen wir, eine tiefe Einbettung von if-Blöcken mit einer anderen Strategie zu vermeiden: Immer wenn wir auf eine Nullvariable stoßen, geben wir den Zeichenfolgenwert "Unbekannt" zurück. Diese Lösung ist aber auch alles andere als ideal; Jetzt hat die Methode vier verschiedene Austrittspunkte, was die Wartung erheblich erschwert. Außerdem wird der im Fall von null zurückgegebene Standardwert - die Zeichenfolge "Unbekannt" - an drei Stellen wiederholt und enthält (wir hoffen) keine Rechtschreibfehler! Um dies zu vermeiden, können Sie die sich wiederholende Zeichenfolge natürlich auf eine Konstante verschieben.

Bild

Darüber hinaus ist dieser Prozess fehleranfällig. Was ist, wenn Sie vergessen, zu überprüfen, ob eine der Eigenschaften null ist? In diesem Kapitel argumentieren wir, dass die Verwendung von Null zur Darstellung eines Wertmangels ein grundlegend falscher Ansatz ist. Eine bessere Methode zur Modellierung des Fehlens und Vorhandenseins von Wert ist erforderlich.

11.1.2. Probleme mit null


Zusammenfassend führt die Verwendung leerer Links in Java zu folgenden theoretischen und praktischen Problemen.

  • Dient als Fehlerquelle. NullPointerException ist die häufigste (mit großem Abstand) Ausnahme in Java.
  • Bläst den Code auf. Die Lesbarkeit verschlechtert sich aufgrund der Notwendigkeit, den Code mit Nullprüfungen zu füllen, die häufig tief verschachtelt sind.
  • Es macht keinen Sinn. Insbesondere fehlt jede semantische Bedeutung, insbesondere ist ein solcher Ansatz zur Modellierung der Bedeutungslosigkeit in einer Sprache mit statischer Typisierung grundsätzlich falsch.
  • Verstößt gegen die Ideologie der Java-Sprache. Java verbirgt immer Zeiger vor Entwicklern, mit einer Ausnahme: einem Nullzeiger.
  • Erzeugt eine Lücke im Typsystem. null enthält keinen Typ oder andere Informationen, daher kann es jedem Linktyp zugewiesen werden. Dies kann zu Problemen beim Übertragen von Null in einen anderen Teil des Systems führen, in dem keine Informationen darüber vorliegen, wie diese Null ursprünglich aussehen sollte.

Als Grundlage für andere mögliche Lösungen im nächsten Unterabschnitt untersuchen wir kurz die Möglichkeiten, die andere Programmiersprachen bieten.

11.1.3. Alternativen zu null in anderen Programmiersprachen


In den letzten Jahren ist es Sprachen wie Groovy gelungen, dieses Problem zu umgehen, indem ein sicherer Anrufbetreiber (?.) Eingeführt wurde, der so konzipiert ist, dass er sicher mit potenziellen Nullwerten arbeitet. Beachten Sie den folgenden Groovy-Code, um zu verstehen, wie dieser Prozess in der Praxis implementiert wird. Es ruft den Namen der Versicherungsgesellschaft ab, bei der die angegebene Person das Auto versichert hat:

def carInsuranceName = person?.car?.insurance?.name

Es sollte Ihnen klar sein, was dieser Code tut. Eine Person hat möglicherweise kein Auto, für dessen Simulation wir der Fahrzeugverknüpfung des Personenobjekts null zuweisen. Ebenso kann eine Maschine nicht versichert sein. Mit dem Safe Call Operator von Groovy können Sie sicher mit potenziell leeren Links arbeiten, ohne eine NullPointerException auszulösen, den leeren Link in der Aufrufkette weiterzuleiten und null zurückzugeben, wenn ein Wert aus der Kette null ist.

Es wurde vorgeschlagen, eine ähnliche Funktion in Java 7 zu implementieren, aber dann wurde beschlossen, dies nicht zu tun. Seltsamerweise wird der Operator für sichere Anrufe in Java jedoch nicht wirklich benötigt. Der erste Gedanke eines Java-Entwicklers, wenn er auf eine NullPointerException stößt, besteht darin, das Problem schnell zu beheben, indem er eine if-Anweisung hinzufügt, um vor dem Aufruf seiner Methode auf null zu prüfen. Das Lösen des Problems auf ähnliche Weise, ohne zu berücksichtigen, ob Null in dieser speziellen Situation für Ihren Algorithmus oder Ihr Datenmodell akzeptabel ist, führt nicht zu einer Korrektur, sondern zu einem Ausblenden von Fehlern. Und anschließend wird es für den nächsten Entwickler (wahrscheinlich Sie selbst in einer Woche oder einem Monat) viel schwieriger sein, diesen Fehler zu finden und zu beheben. Tatsächlich fegen Sie den Müll einfach unter den Teppich. Groovys null-sicherer Dereferenzierungsoperator ist nur ein größerer und leistungsfähigerer Besen, der solche dummen Dinge tun kann, ohne sich um die Konsequenzen sorgen zu müssen.

Andere funktionale Programmiersprachen wie Haskell und Scala betrachten dieses Problem anders. Haskell hat einen Maybe-Typ, der im Wesentlichen einen optionalen Wert enthält. Ein Objekt vom Typ Möglicherweise enthält es einen Wert des angegebenen Typs oder nichts. Haskell fehlt das Konzept eines leeren Links. In Scala wird zum Einkapseln des Vorhandenseins oder Nichtvorhandenseins eines Werts vom Typ T eine ähnliche logische Strukturoption [T] bereitgestellt, die in Kapitel 20 erläutert wird. In diesem Fall müssen Sie explizit überprüfen, ob ein Wert vorhanden ist, indem Sie Operationen vom Typ Option verwenden, die "Nullprüfungen" bereitstellen. . Jetzt ist es nicht mehr möglich, die Überprüfung auf Null zu vergessen, da das Typsystem selbst eine Überprüfung erfordert.

Okay, wir sind ein wenig abseits des Themas und alles klingt ziemlich abstrakt. Sie fragen sich wahrscheinlich, was Java 8 in diesem Sinne bietet. Inspiriert von der Idee eines optionalen Werts haben die Entwickler von Java 8 eine neue Klasse eingeführt, java.util.Optional! In diesem Kapitel zeigen wir, welchen Vorteil es hat, potenziell fehlende Werte zu modellieren, anstatt ihnen eine leere Referenz zuzuweisen. Wir werden auch erklären, warum ein solcher Übergang von null zu optional es erfordert, dass der Programmierer die Ideologie der Arbeit mit optionalen Werten im Domänenmodell überprüft. Abschließend werden wir die Möglichkeiten dieser neuen optionalen Klasse untersuchen und einige praktische Beispiele für ihre effektive Verwendung liefern. Als Ergebnis lernen Sie, wie Sie verbesserte APIs entwerfen, bei denen der Benutzer bereits anhand der Methodensignatur versteht, ob hier ein optionaler Wert möglich ist.

11.2. Einführung in die optionale Klasse


In Java 8 scheint unter dem Einfluss der Sprachen Haskell und Scala eine neue Klasse java.util.Optional einen optionalen Wert zu kapseln. Wenn Sie beispielsweise wissen, dass eine Person ein Auto besitzen kann oder nicht, sollten Sie das variable Auto in der Personenklasse nicht mit dem Typ Auto deklarieren und ihm eine leere Referenz zuweisen, wenn die Person kein Auto hat. Stattdessen sollte der Typ optional sein, wie in Abb. 1 dargestellt. 11.1.

Bild

Bei einem bestimmten Wert dient die optionale Klasse als Adapter dafür. Umgekehrt wird das Fehlen eines Werts mithilfe der leeren Option modelliert, die von der Methode Optional.empty zurückgegeben wird. Diese statische Factory-Methode gibt eine spezielle Einzelinstanz der Klasse Optional zurück. Sie fragen sich möglicherweise, was der Unterschied zwischen einem leeren Link und Optional.empty () ist. Semantisch können sie als ein und dasselbe betrachtet werden, aber in der Praxis gibt es enorme Unterschiede zwischen ihnen. Ein Versuch, null zu dereferenzieren, führt unweigerlich zu einer NullPointerException, und Optional.empty () ist ein gültiges, funktionsfähiges Objekt vom Typ Optional, auf das bequem zugegriffen werden kann. Bald werden Sie sehen, wie genau.

Ein wichtiger praktischer semantischer Unterschied bei der Verwendung von optionalen Objekten anstelle von Null: Die Deklaration einer Variablen vom Typ Optional besagt eindeutig, dass an dieser Stelle ein leerer Wert zulässig ist. Und umgekehrt, wenn Sie immer den Autotyp verwenden und möglicherweise manchmal leere Verweise auf Variablen dieses Typs zuweisen, meinen Sie, dass Sie sich nur auf Ihr Domänenwissen verlassen, um zu verstehen, ob null zur Definitionsdomäne einer bestimmten Variablen gehört.

In diesem Sinne können Sie das Originalmodell aus Listing 11.1 wiederholen. Wir verwenden die optionale Klasse, wie in Listing 11.4 gezeigt.

Beachten Sie, dass die Verwendung der Klasse Optional die Semantik des Modells bereichert. Ein optionales Feld in der Personenklasse und ein optionales Feld in der Fahrzeugklasse spiegeln die Tatsache wider, dass eine Person ein Auto haben kann oder nicht, genauso wie eine Maschine versichert sein kann oder nicht.

Gleichzeitig bedeutet die Angabe des Namens der Versicherungsgesellschaft als String und nicht als optional, dass die Versicherungsgesellschaft einen Namen haben muss. Somit wissen Sie sicher, dass Sie eine NullPointerException erhalten, wenn Sie den Namen der Versicherungsgesellschaft dereferenzieren. Das Hinzufügen einer Nullprüfung ist nicht erforderlich, da dies nur das Problem verbergen würde. Die Versicherungsgesellschaft sollte einen Namen haben. Wenn Sie also auf eine Firma ohne Namen stoßen, müssen Sie herausfinden, was mit den Daten nicht stimmt, und keinen Code hinzufügen, um diese Tatsache zu verbergen.

Bild

Durch die konsequente Verwendung optionaler Werte wird klar zwischen einem möglicherweise fehlenden Wert und einem Wert unterschieden, der aufgrund eines Fehlers im Algorithmus oder eines Problems in den Daten fehlt. Es ist wichtig zu beachten, dass die optionale Klasse nicht alle leeren Links durch einen einzigen ersetzen soll. Seine Aufgabe ist es, verständlichere APIs zu entwerfen, damit anhand der Signatur der Methode verstanden werden kann, ob dort ein optionaler Wert gefunden werden kann. Das Java-Typsystem erzwingt eine Entpackoption, um das Fehlen eines Werts zu behandeln.

Über Autoren


Raul-Gabriel Urma ist CEO und Mitbegründer von Cambridge Spark (UK), einer führenden Bildungsgemeinschaft für Datenforscher und -entwickler. Raul wurde 2017 zu einem der Teilnehmer des Java Champions-Programms gewählt. Er hat für Google, eBay, Oracle und Goldman Sachs gearbeitet. Er verteidigte seine Diplomarbeit in Computertechnik an der Universität Cambridge. Darüber hinaus hat er einen Master-Abschluss in Ingenieurwissenschaften vom Imperial College London, einen Abschluss mit Auszeichnung und mehrere Auszeichnungen für Rationalisierungsvorschläge erhalten. Raul hat auf internationalen Konferenzen mehr als 100 technische Berichte vorgelegt.

Mario Fusco ist Senior Software Engineer bei Red Hat und an der Entwicklung des Drools-Kernels, der JBoss-Regel-Engine, beteiligt. Er verfügt über umfangreiche Erfahrung in der Java-Entwicklung und war (häufig als führender Entwickler) an vielen Unternehmensprojekten in verschiedenen Branchen beteiligt, von den Medien bis zum Finanzsektor. Zu seinen Interessen gehören funktionale Programmierung und domänenspezifische Sprachen. Basierend auf diesen beiden Hobbys erstellte er die Open-Source-Lambdaj-Bibliothek, um ein internes Java-DSL für die Arbeit mit Sammlungen zu entwickeln und die Verwendung einiger Elemente der funktionalen Programmierung in Java zu ermöglichen.

Alan Mycroft ist Professor am Institut für Informatik der Universität Cambridge, wo er seit 1984 unterrichtet. Er ist außerdem Mitarbeiter des Robinson College, einer der Gründer der Europäischen Vereinigung für Programmiersprachen und -systeme, und einer der Gründer und Treuhänder der Raspberry Pi Foundation. Er hat Abschlüsse in Mathematik (Cambridge) und Informatik (Edinburgh). Alan ist Autor von mehr als 100 wissenschaftlichen Artikeln. Er war der Betreuer von über 20 Doktoranden. Seine Forschung befasst sich hauptsächlich mit dem Bereich der Programmiersprachen und deren Semantik, Optimierung und Implementierung. Eine Zeit lang arbeitete er bei AT & T Laboratories und der Forschungsabteilung von Intel und war Mitbegründer von Codemist Ltd., die den C-Sprach-Compiler für die ARM-Architektur namens Norcroft herausbrachte.

»Weitere Informationen zum Buch finden Sie auf der Website des Herausgebers
» Inhalt
» Auszug

25% Rabatt auf Gutschein für Händler - Java

Nach Bezahlung der Papierversion des Buches wird ein elektronisches Buch per E-Mail verschickt.

Source: https://habr.com/ru/post/de470598/


All Articles