Warum sollte Optional in Java 8+ anstelle von herkömmlichen Nullzeigerprüfungen verwendet werden?

110

Wir sind kürzlich zu Java 8 übergegangen. Jetzt sehe ich Anwendungen, die mit OptionalObjekten überflutet sind.

Vor Java 8 (Style 1)

Employee employee = employeeServive.getEmployee();

if(employee!=null){
    System.out.println(employee.getId());
}

Nach Java 8 (Style 2)

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employee = employeeOptional.get();
    System.out.println(employee.getId());
}

Ich sehe keinen Mehrwert, Optional<Employee> employeeOptional = employeeService.getEmployee();wenn der Dienst selbst optional zurückgibt:

Ich komme aus Java 6 und sehe Stil 1 klarer und mit weniger Codezeilen. Gibt es einen wirklichen Vorteil, den ich hier vermisse?

Konsolidiert aus dem Verständnis aller Antworten und weiteren Recherchen im Blog

user3198603
quelle
20
Oracle hat einen umfangreichen Artikel über die Verwendung von Optional .
Mike Partridge
28
Yuck! employeeOptional.isPresent()scheint der Punkt der Optionstypen völlig zu fehlen. Laut @ MikePartridges Kommentar ist dies weder empfohlen noch idiomatisch. Das explizite Überprüfen, ob ein Optionstyp etwas oder nichts ist, ist ein Nein-Nein. Sie sollen einfach mapoder flatMapüber sie.
Andres F.
7
Lesen Sie eine Stapelüberlauf- AntwortOptional von Brian Goetz , dem Java Language Architect bei Oracle.
Basil Bourque
6
Dies passiert, wenn Sie Ihr Gehirn im Namen von "Best Practices" ausschalten.
immibis
9
Das ist eine ziemlich schlechte Verwendung von optional, aber selbst das hat Vorteile. Mit nulls kann alles null sein, daher müssen Sie ständig prüfen, ob diese bestimmte Variable wahrscheinlich fehlt. Prüfen Sie, ob dies der Fall ist
Richard Tingle

Antworten:

108

Style 2 ist für Java 8 nicht ausreichend, um den vollen Nutzen zu erzielen. Du willst das if ... useüberhaupt nicht. Siehe Oracle Beispiele . Wenn wir ihren Rat befolgen, bekommen wir:

Stil 3

// Changed EmployeeServive to return an optional, no more nulls!
Optional<Employee> employee = employeeServive.getEmployee();
employee.ifPresent(e -> System.out.println(e.getId()));

Oder ein längerer Ausschnitt

Optional<Employee> employee = employeeServive.getEmployee();
// Sometimes an Employee has forgotten to write an up-to-date timesheet
Optional<Timesheet> timesheet = employee.flatMap(Employee::askForCurrentTimesheet); 
// We don't want to do the heavyweight action of creating a new estimate if it will just be discarded
client.bill(timesheet.orElseGet(EstimatedTimesheet::new));
Caleth
quelle
19
in der Tat verbieten wir die meisten Verwendungen von isPresent (abgefangen von der Codeüberprüfung)
jk.
10
@jk. in der Tat, wenn es eine Überlastung void ifPresent(Consumer<T>, Runnable)gäbe, würde ich für ein vollständiges Verbot von isPresentundget
Caleth,
10
@Caleth: Vielleicht interessieren Sie sich für Java 9 ifPresentOrElse(Consumer<? super T>, Runnable).
Wchargin
9
Ich finde einen Nachteil in diesem Java 8-Stil, verglichen mit dem Java 6-Stil: Er täuscht Code-Coverage-Tools. Mit der if-Anweisung wird angezeigt, wann Sie eine Verzweigung verpasst haben, nicht hier.
Thierry
14
@Aaron "reduziert die Lesbarkeit von Code drastisch". Welcher Teil verringert die Lesbarkeit genau? Klingt eher nach einem "Ich habe es immer anders gemacht und ich möchte meine Gewohnheiten nicht ändern" als nach objektiven Überlegungen.
Voo
47

Wenn Sie Optionalals "Kompatibilitätsebene" eine ältere API verwenden, die möglicherweise noch zurückgegeben wird null, kann es hilfreich sein, die Option (nicht leer) zu erstellen, wenn Sie sich sicher sind , dass Sie über etwas verfügen. ZB wo du geschrieben hast:

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

Ich würde mich entscheiden für:

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

Der Punkt ist, dass Sie wissen, dass es einen Nicht-Null-Angestellten- Service gibt , so dass Sie das in einem Optionalmit einschließen können Optional.of(). Wenn Sie darauf zurückgreifen getEmployee(), erhalten Sie möglicherweise einen Mitarbeiter oder nicht. Dieser Mitarbeiter hat möglicherweise (oder möglicherweise auch nicht) einen Ausweis. Wenn Sie dann eine ID erhalten haben, möchten Sie diese ausdrucken.

Es gibt keine Notwendigkeit, explizit für überprüft jeden null, Präsenz, usw., in diesem Code.

Joshua Taylor
quelle
4
Was ist der Vorteil von (...).ifPresent(aMethod)over if((...).isPresent()) { aMethod(...); }? Es scheint einfach noch eine andere Syntax zu sein, um fast die gleiche Operation auszuführen.
Ruslan
2
@ Ruslan Warum eine spezielle Syntax für einen bestimmten Aufruf verwenden, wenn Sie bereits eine allgemeine Lösung haben, die für alle Fälle gleich gut funktioniert? Wir wenden hier nur die gleiche Idee von Map und Co an.
Voo
1
@Ruslan ... eine API gibt Nullen anstelle von leeren Sammlungen oder Arrays zurück. Sie können beispielsweise Folgendes tun (vorausgesetzt, eine Parent-Klasse, für die getChildren () eine Menge von untergeordneten Elementen zurückgibt, oder null, wenn keine untergeordneten Elemente vorhanden sind): Set<Children> children = Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet); Jetzt kann ich childrenz children.forEach(relative::sendGift);. B. einheitlich verarbeiten . Die könnten auch kombiniert werden: Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet).forEach(relative::sendGift);. Alle die Boilerplate der Nullprüfung usw.
Joshua Taylor
1
@Ruslan ... wird an bewährten Code in der Standardbibliothek delegiert. Das reduziert die Menge an Code, die ich als Programmierer schreiben, testen, debuggen usw. muss
Joshua Taylor
1
Froh, dass ich Swift benutze. if let employee = employeeService.employee {print (employee.Id); }
gnasher729
23

Es ist so gut wie kein Mehrwert, Optionaleinen einzigen Wert zu haben. Wie Sie gesehen haben, ersetzt dies nur einen Scheck auf nulldurch einen Scheck auf Anwesenheit.

Es ist ein enormer Mehrwert, Collectionsolche Dinge zu haben. Alle Streaming-Methoden, die eingeführt wurden, um Low-Level-Loops alten Stils zu ersetzen, verstehen die OptionalDinge und machen das Richtige daraus (sie werden nicht verarbeitet oder geben letztendlich eine andere nicht gesetzte zurück Optional). Wenn Sie sich häufiger mit n Elementen beschäftigen als mit einzelnen Elementen (und 99% aller realistischen Programme), ist es eine enorme Verbesserung, über eine integrierte Unterstützung für optional vorhandene Elemente zu verfügen. Im Idealfall müssen Sie nie nachsehen null oder anwesend sein.

Kilian Foth
quelle
24
Neben Sammlungen / Streams eignet sich ein optionales Element auch hervorragend, um APIs selbstdokumentierender zu gestalten, z . B. Employee getEmployee()vs. Optional<Employee> getEmployee()Während technisch beide zurückkehren könnten null, ist es weitaus wahrscheinlicher, dass einer von ihnen Probleme verursacht.
amon
16
Es gibt viel Wert in einem optionalen Einzelwert. Zu können, .map()ohne auf dem ganzen Weg nach null suchen zu müssen, ist großartig. ZB Optional.of(service).map(Service::getEmployee).map(Employee::getId).ifPresent(this::submitIdForReview);.
Joshua Taylor
9
Ich bin nicht einverstanden mit Ihrer ersten Bemerkung, dass es keinen Mehrwert gibt. Optional bietet Kontext zu Ihrem Code. Mit einem kurzen Blick können Sie feststellen, ob eine Funktion korrekt ist, und alle Fälle werden abgedeckt. Sollten Sie bei der Nullprüfung jeden einzelnen Referenztyp für jede einzelne Methode auf Null prüfen? Ein ähnlicher Fall kann auf Klassen und öffentliche / private Modifikatoren angewendet werden. Sie fügen Ihrem Code einen Wert von Null hinzu, richtig?
Kunst
3
@JoshuaTaylor Ehrlich gesagt, im Vergleich zu diesem Ausdruck bevorzuge ich den altmodischen Scheck für null.
Kilian Foth
2
Ein weiterer großer Vorteil von Optional auch bei Einzelwerten ist, dass Sie nicht vergessen können, auf Null zu prüfen, wenn Sie dies tun sollten. Ansonsten ist es sehr leicht, den Scheck zu vergessen und am Ende eine NullPointerException zu erhalten ...
Sean Burton
17

Solange Sie Optionalwie eine ausgefallene API für verwenden isNotNull(), werden Sie keine Unterschiede feststellen, wenn Sie nur nachsehen null.

Dinge , die Sie sollten nicht verwenden Optionalfür

Mit Optionalnur auf das Vorhandensein eines Wertes zu überprüfen , ist Bad - Code ™:

// BAD CODE ™ --  just check getEmployee() != null  
Optional<Employee> employeeOptional =  Optional.ofNullable(employeeService.getEmployee());  
if(employeeOptional.isPresent()) {  
    Employee employee = employeeOptional.get();  
    System.out.println(employee.getId());
}  

Dinge , die Sie verwenden sollten Optionalfür

Vermeiden Sie, dass ein Wert nicht vorhanden ist, indem Sie bei Verwendung von null-zurückgegebenen API-Methoden bei Bedarf einen erstellen:

Employee employee = Optional.ofNullable(employeeService.getEmployee())
                            .orElseGet(Employee::new);
System.out.println(employee.getId());

Oder wenn das Erstellen eines neuen Mitarbeiters zu kostspielig ist:

Optional<Employee> employee = Optional.ofNullable(employeeService.getEmployee());
System.out.println(employee.map(Employee::getId).orElse("No employee found"));

Machen Sie außerdem alle darauf aufmerksam, dass Ihre Methoden möglicherweise keinen Wert zurückgeben (falls Sie aus irgendeinem Grund keinen Standardwert wie oben zurückgeben können):

// Your code without Optional
public Employee getEmployee() {
    return someCondition ? null : someEmployee;
}
// Someone else's code
Employee employee = getEmployee(); // compiler doesn't complain
// employee.get...() -> NPE awaiting to happen, devs criticizing your code

// Your code with Optional
public Optional<Employee> getEmployee() {
    return someCondition ? Optional.empty() : Optional.of(someEmployee);
}
// Someone else's code
Employee employee = getEmployee(); // compiler complains about incompatible types
// Now devs either declare Optional<Employee>, or just do employee = getEmployee().get(), but do so consciously -- not your fault.

Und schließlich gibt es all Stream-ähnliche Methoden , die bereits in anderen Antworten erklärt worden, obwohl diejenigen , die nicht wirklich sind Sie entscheiden , zu verwenden , Optionalaber die Verwendung der Herstellung Optionalvon jemandem anderen zur Verfügung gestellt.

walen
quelle
1
@Kapep In der Tat. Wir hatten diese Debatte um / r / java und ich sagte : «Ich sehe Optionaleine verpasste Gelegenheit. "Hier, haben Sie dieses neue OptionalDing, das ich gemacht habe, damit Sie gezwungen sind, nach Nullwerten zu suchen. Oh, und übrigens ... Ich habe eine get()Methode hinzugefügt , damit Sie nicht wirklich nach irgendetwas suchen müssen;);)" . (...) Optionalist schön als "DIESES KÖNNTE NULL SEIN" -Neonschild, aber die bloße Existenz seiner get()Methode lähmt es » . Aber OP fragte nach Gründen für die Verwendung Optional, nicht nach Gründen dagegen oder nach Konstruktionsfehlern.
Walen
1
Employee employee = Optional.ofNullable(employeeService.getEmployee());Sollte der Typ in Ihrem dritten Snippet nicht so sein Optional<Employee>?
Charlie Harding
2
@immibis Inwiefern verkrüppelt? Der Hauptgrund für die Verwendung getist, wenn Sie mit altem Code interagieren müssen. Ich kann mir nicht viele triftige Gründe für die Verwendung von neuem Code vorstellen get. Das heißt, wenn man mit altem Code interagiert, gibt es oft keine Alternative, aber walen hat den Punkt, dass die Existenz getAnfängern das Schreiben von schlechtem Code verursacht.
Voo
1
@immibis Ich habe es nicht Mapeinmal erwähnt und mir ist nicht bekannt, dass jemand eine so drastische, nicht abwärtskompatible Änderung vornimmt. Sie können also absichtlich schlechte APIs entwickeln, Optionalohne sicher zu sein, was dies beweist. Eine Methode Optionalwäre allerdings sinnvoll tryGet.
Voo
2
@Voo Mapist ein Beispiel, mit dem jeder vertraut ist. Natürlich können sie aus Map.getGründen der Abwärtskompatibilität kein Optional zurückgeben, aber Sie können sich leicht eine andere Map-ähnliche Klasse vorstellen, für die solche Kompatibilitätsbeschränkungen nicht gelten. Sprich class UserRepository {Optional<User> getUserDetails(int userID) {...}}. Jetzt möchten Sie die Details des angemeldeten Benutzers abrufen, und Sie wissen, dass diese vorhanden sind, da sie sich anmelden konnten und Ihr System niemals Benutzer löscht. Also tust du es theUserRepository.getUserDetails(loggedInUserID).get().
immibis
12

Der entscheidende Punkt für mich bei der Verwendung von Optional ist, dass klar bleibt, wann der Entwickler überprüfen muss, ob die Funktion einen Wert zurückgibt oder nicht.

//service 1
Optional<Employee> employeeOptional = employeeService.getEmployee();
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

//service 2
Employee employee = employeeService.getEmployeeXTPO();
System.out.println(employee.getId());
Rodrigo Menezes
quelle
8
Es verwirrt mich, dass all das raffinierte funktionale Zeug mit Optionen, obwohl es wirklich schön ist, sie zu haben, als wichtiger erachtet wird als dieser Punkt hier. Vor Java 8 mussten Sie Javadoc durchsuchen, um zu wissen, ob Sie die Antwort eines Anrufs auf Null überprüfen mussten. Nach Java 8 wissen Sie vom Rückgabetyp, ob Sie "nichts" zurückbekommen können oder nicht.
Buhb
3
Nun, es gibt das "alte Code" -Problem, bei dem die Dinge immer noch null zurückgeben könnten ... aber ja, es ist großartig, anhand der Signatur zu erkennen.
Haakon Løtveit
5
Ja, es funktioniert auf jeden Fall besser in Sprachen, in denen optionales <T> der einzige Weg ist, um auszudrücken, dass ein Wert fehlen könnte, dh Sprachen ohne null ptr.
Dureuill
8

Ihr Hauptfehler ist, dass Sie immer noch prozeduraler denken. Dies ist nicht als Kritik an Ihnen als Person gedacht, sondern lediglich eine Beobachtung. Funktionaleres Denken ist mit Zeit und Übung verbunden, und daher sind die Methoden „Präsentieren“ und „Schauen“, wie die offensichtlichsten richtigen Dinge aussehen, die für Sie in Frage kommen. Ihr zweiter kleiner Fehler ist das Erstellen Ihrer Option in Ihrer Methode. Die Option soll dokumentieren, dass etwas einen Wert zurückgeben kann oder nicht. Sie könnten nichts bekommen.

Dies hat dazu geführt, dass Sie perfekt lesbaren Code geschrieben haben, der absolut vernünftig erscheint, aber Sie wurden von den abscheulichen Zwillingsversuchern verführt, die get und isPresent sind.

Natürlich wird die Frage schnell: "Warum sind wir präsent und kommen auch dort an?"

Eine Sache, die viele Leute hier vermissen, ist, dass isPresent () ist, dass es nicht für neuen Code gemacht ist, der von Leuten geschrieben wurde, die voll an Bord sind und wissen, wie verdammt nützliche Lambdas sind und wer die funktionale Sache mag.

Es gibt uns jedoch ein paar (zwei) gute, großartige, glamouröse (?) Vorteile:

  • Es vereinfacht den Übergang von altem Code, um die neuen Funktionen zu nutzen.
  • Es erleichtert die Lernkurven von Optional.

Der erste ist ziemlich einfach.

Stellen Sie sich vor, Sie haben eine API, die so aussieht:

public interface SnickersCounter {
  /** 
   * Provides a proper count of how many snickers have been consumed in total.
   */
  public SnickersCount howManySnickersHaveBeenEaten();

  /**
    * returns the last snickers eaten.<br>
    * If no snickers have been eaten null is returned for contrived reasons.
    */
  public Snickers lastConsumedSnickers();
}

Und Sie hatten eine Legacy-Klasse, die dies als solches verwendete (füllen Sie die Lücken aus):

Snickers lastSnickers = snickersCounter.lastConsumedSnickers();
if(null == lastSnickers) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers);
}

Ein erfundenes Beispiel, um sicher zu sein. Aber ertrage es hier mit mir.

Java 8 wurde nun gestartet und wir bemühen uns, an Bord zu kommen. Eines der Dinge, die wir tun, ist, dass wir unsere alte Schnittstelle durch etwas ersetzen möchten, das Optional zurückgibt. Warum? Denn wie jemand anderes schon gnädig gesagt hat: Dies nimmt die Vermutung auf sich, ob etwas null sein kann oder nicht. Dies wurde bereits von anderen hervorgehoben. Aber jetzt haben wir ein Problem. Stellen Sie sich vor, wir haben (entschuldigen Sie, während ich bei einer unschuldigen Methode Alt + F7 drücke) 46 Stellen, an denen diese Methode in gut getestetem Legacy-Code aufgerufen wird, der ansonsten hervorragende Arbeit leistet. Jetzt müssen Sie alle aktualisieren.

Hier erstrahlt isPresent.

Denn jetzt: Snickers lastSnickers = snickersCounter.lastConsumedSnickers (); if (null == lastSnickers) {wirf eine neue NoSuchSnickersException (); } else {consumer.giveDiabetes (lastSnickers); }

wird:

Optional<Snickers> lastSnickers = snickersCounter.lastConsumedSnickers();
if(!lastSnickers.isPresent()) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers.get());
}

Und dies ist eine einfache Änderung, die Sie dem neuen Junior geben können: Er kann etwas Nützliches tun und gleichzeitig die Codebasis erkunden. Win-Win. Immerhin ist so etwas ziemlich verbreitet. Und jetzt müssen Sie den Code nicht mehr umschreiben, um Lambdas oder ähnliches zu verwenden. (In diesem speziellen Fall wäre es trivial, aber ich überlasse es dem Leser, sich Beispiele auszudenken, bei denen es als Übung schwierig wäre.)

Beachten Sie, dass dies bedeutet, dass die Art und Weise, wie Sie dies getan haben, im Wesentlichen ein Weg ist, mit Legacy-Code umzugehen, ohne kostspielige Umschreibungen vorzunehmen. Was ist mit neuem Code?

Nun, in Ihrem Fall, in dem Sie nur etwas ausdrucken möchten, tun Sie einfach Folgendes:

snickersCounter.lastConsumedSnickers (). ifPresent (System.out :: println);

Das ist ziemlich einfach und vollkommen klar. Der Punkt, der dann langsam an die Oberfläche sprudelt, ist, dass es Use Cases für get () und isPresent () gibt. Mit ihnen können Sie vorhandenen Code mechanisch ändern, um die neueren Typen zu verwenden, ohne zu viel darüber nachzudenken. Was Sie tun, ist daher auf folgende Weise falsch:

  • Sie rufen eine Methode auf, die möglicherweise null zurückgibt. Die richtige Idee wäre, dass die Methode null zurückgibt.
  • Sie verwenden die herkömmlichen Bandaid-Methoden, um dieses optionale Problem zu lösen, anstatt die leckeren neuen Methoden zu verwenden, die Lambda-Phantasie enthalten.

Wenn Sie Optional als einfache Null-Sicherheitsüberprüfung verwenden möchten, sollten Sie Folgendes tun:

new Optional.ofNullable(employeeServive.getEmployee())
    .map(Employee::getId)
    .ifPresent(System.out::println);

Natürlich sieht die gut aussehende Version so aus:

employeeService.getEmployee()
    .map(Employee::getId)
    .ifPresent(System.out::println);

Übrigens, obwohl es keinesfalls erforderlich ist, empfehle ich, pro Operation eine neue Zeile zu verwenden, damit es einfacher zu lesen ist. Leicht zu lesende und verständliche Beats prägnant an jedem Wochentag.

Dies ist natürlich ein sehr einfaches Beispiel, in dem es einfach ist, alles zu verstehen, was wir versuchen zu tun. Im wirklichen Leben ist es nicht immer so einfach. Beachten Sie jedoch, dass wir in diesem Beispiel unsere Absichten zum Ausdruck bringen. Wir möchten den Mitarbeiter abrufen, seinen Ausweis abrufen und ihn, falls möglich, ausdrucken. Dies ist der zweite große Gewinn mit Optional. Dadurch können wir klareren Code erstellen. Ich denke auch, dass es im Allgemeinen eine gute Idee ist, Dinge wie die Erstellung einer Methode zu tun, die eine Reihe von Dingen ausführt, damit Sie sie einer Karte hinzufügen können.

Haakon Løtveit
quelle
1
Das Problem dabei ist, dass Sie versuchen, das so klingen zu lassen, als wären diese funktionalen Versionen genauso lesbar wie die älteren Versionen. In einem Fall wurde sogar gesagt, ein Beispiel sei "vollkommen klar". Das ist nicht der Fall. Dieses Beispiel war klar, aber nicht perfekt , da es mehr Nachdenken erfordert. map(x).ifPresent(f)macht einfach nicht den gleichen intuitiven Sinn, den man if(o != null) f(x)hat. Die ifVersion ist völlig klar. Dies ist ein weiterer Fall von Leuten, die auf einem beliebten Musikwagen springen, * kein * Fall von wirklichem Fortschritt.
Aaron
1
Es ist zu beachten, dass ich nicht sage, dass die Verwendung der funktionalen Programmierung oder von keinen Nutzen bringt Optional. Ich bezog mich nur auf die Verwendung von optional in diesem speziellen Fall und mehr noch auf die Lesbarkeit, da mehrere Personen so handeln, dass dieses Format gleich oder besser lesbar ist als if.
Aaron
1
Es hängt von unseren Vorstellungen von Klarheit ab. Sie machen zwar sehr gute Punkte, aber ich möchte auch darauf hinweisen, dass Lambdas für viele Menschen irgendwann neu und seltsam waren. Ich würde es wagen, einen Internet-Cookie zu setzen, von dem einige sagen, er sei präsent und erhalte eine wundervolle Erleichterung: Sie könnten jetzt mit der Aktualisierung des Legacy-Codes fortfahren und später die Lambda-Syntax erlernen. Andererseits waren Menschen mit Lisp, Haskell oder ähnlichen Sprachkenntnissen wahrscheinlich erfreut, dass sie nun vertraute Muster auf ihre Probleme in Java anwenden konnten ...
Haakon Løtveit
@ Aaron - Klarheit ist nicht der Punkt der optionalen Typen. Ich persönlich stelle fest, dass sie die Klarheit nicht verringern , und es gibt einige Fälle (wenn auch nicht in diesem Fall), in denen sie die Klarheit erhöhen, aber der Hauptvorteil ist, dass sie (solange Sie sie meiden isPresentund getso viel wie möglich realistisch sind) die Sicherheit verbessern . Es ist schwieriger, falschen Code zu schreiben, der das Fehlen eines Werts nicht überprüft, wenn der Typ Optional<Whatever>keine Nullable verwendet Whatever.
Jules
Auch wenn Sie sofort die Werte herausnehmen, solange Sie nicht verwendenget , sind Sie gezwungen , zu entscheiden , was zu tun ist, wenn ein fehlender Wert auftritt: Sie würden entweder verwenden orElse()(oder orElseGet()) eine Standard zu liefern, oder orElseThrow()ein werfen gewählt Ausnahme, die die Art des Problems dokumentiert. Dies ist besser als eine generische NPE, die bedeutungslos ist, bis Sie in die Stapelablaufverfolgung eingreifen.
Jules
0

Ich sehe keinen Vorteil in Stil 2. Sie müssen immer noch erkennen, dass Sie die Nullprüfung benötigen, und jetzt ist sie noch größer und daher weniger lesbar.

Ein besserer Stil wäre, wenn employeeServive.getEmployee () den Wert Optional zurückgibt und der Code dann wie folgt lautet:

Optional<Employee> employeeOptional = employeeServive.getEmployee();
if(employeeOptional.isPresent()){
    Employee employee= employeeOptional.get();
    System.out.println(employee.getId());
}

Auf diese Weise können Sie "employeeServive.getEmployee (). GetId ()" nicht aufrufen, und Sie bemerken, dass Sie mit optionalem Wert irgendwie umgehen sollten. Und wenn in Ihrer gesamten Codebasis Methoden nie null zurückgeben (oder fast nie, wenn Ihrem Team Ausnahmen von dieser Regel bekannt sind), erhöht dies die Sicherheit gegen NPE.

user470365
quelle
12
Optional wird eine sinnlose Komplikation, sobald Sie verwenden isPresent(). Wir verwenden optional, damit wir nicht testen müssen.
candied_orange
2
Wie andere gesagt haben, nicht verwenden isPresent(). Dies ist der falsche Ansatz für Optionen. Verwenden Sie stattdessen mapoder flatMap.
Andres F.
1
@CandiedOrange Und doch ist die am häufigsten gewählte Antwort (Sprichwort zu verwenden ifPresent) nur eine andere Möglichkeit zum Testen.
immibis
1
@CandiedOrange ifPresent(x)ist genauso ein Test wie if(present) {x}. Der Rückgabewert ist irrelevant.
immibis
1
@CandiedOrange Trotzdem hast du ursprünglich gesagt, "damit wir nicht testen müssen." Sie können nicht sagen, "wir müssen also nicht testen", wenn Sie tatsächlich noch testen ... was Sie sind.
Aaron
0

Ich denke, der größte Vorteil ist, dass wenn Ihre Methodensignatur eine zurückgibt, EmployeeSie nicht auf Null prüfen. Sie wissen durch diese Unterschrift, dass die Rückgabe eines Mitarbeiters garantiert ist. Sie müssen keinen Fehlerfall behandeln. Mit einem optionalen wissen Sie, was Sie tun.

In Code, den ich gesehen habe, gibt es Nullprüfungen, die niemals fehlschlagen werden, da die Benutzer den Code nicht nachverfolgen möchten, um festzustellen, ob eine Null möglich ist, sodass sie überall defensiv Nullprüfungen durchführen. Das macht den Code etwas langsamer, aber was noch wichtiger ist, es macht den Code viel lauter.

Damit dies funktioniert, müssen Sie dieses Muster jedoch konsistent anwenden.

Schlitten
quelle
1
Je einfacher Weg , dies zu erreichen , verwendet @Nullableund @ReturnValuesAreNonnullByDefaultzusammen mit dem statischen Analyse.
Maaartinus
@maaartinus Ist das Hinzufügen zusätzlicher Tools in Form einer statischen Analyse und Dokumentation in den Decorator-Annotationen einfacher als die Unterstützung der API zur Kompilierungszeit?
Schlitten
Das Hinzufügen zusätzlicher Tools ist in der Tat einfacher, da keine Codeänderungen erforderlich sind. Außerdem willst du es trotzdem haben, da es auch andere Bugs fängt. +++ Ich schrieb einen trivialen Skript Hinzufügen package-info.javamit @ReturnValuesAreNonnullByDefaultallen meinen Paketen, so alles , was ich tun müssen, ist hinzuzufügen , @Nullablewo Sie wechseln müssen Optional. Dies bedeutet weniger Zeichen, keinen zusätzlichen Müll und keinen Laufzeitaufwand. Ich muss die Dokumentation nicht nachschlagen, da sie beim Bewegen des Mauszeigers über die Methode angezeigt wird. Das statische Analysetool erkennt Fehler und spätere Änderungen der Nichtigkeit.
Maaartinus
Meine größte Sorge Optionalist, dass es eine alternative Möglichkeit zum Umgang mit Nullability bietet. Wenn es von Anfang an in Java war, mag es in Ordnung sein, aber jetzt ist es nur ein Schmerz. Den Kotlin-Weg zu gehen, wäre meiner Meinung nach viel besser. +++ Beachten Sie, dass dies List<@Nullable String>immer noch eine Liste von Zeichenfolgen ist, während dies List<Optional<String>>nicht der Fall ist.
Maaartinus
0

Meine Antwort lautet: Tu es einfach nicht. Überlegen Sie sich mindestens zweimal, ob es sich wirklich um eine Verbesserung handelt.

Ein Ausdruck (entnommen aus einer anderen Antwort) wie

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

scheint die richtige funktionale Art zu sein, mit ursprünglich nullfähigen Rückgabemethoden umzugehen. Es sieht gut aus, wirft nie und bringt Sie nie dazu, über die Behandlung des "abwesenden" Falls nachzudenken.

Es sieht besser aus als im alten Stil

Employee employee = employeeService.getEmployee();
if (employee != null) {
    ID id = employee.getId();
    if (id != null) {
        System.out.println(id);
    }
}

das macht es offensichtlich, dass es elseKlauseln geben kann .

Der funktionale Stil

  • sieht völlig anders aus (und ist nicht lesbar für Leute, die nicht daran gewöhnt sind)
  • ist ein Schmerz zu debuggen
  • kann nicht einfach erweitert werden, um den "abwesenden" Fall zu behandeln (reicht orElsenicht immer aus)
  • irreführend, wenn der Fall "abwesend" ignoriert wird

In keinem Fall sollte es als Allheilmittel verwendet werden. Wenn es universell anwendbar war, sollte die Semantik des .Operators durch die Semantik des ?.Operators ersetzt , die NPE vergessen und alle Probleme ignoriert werden.


Obwohl ich ein großer Java-Fan bin, muss ich sagen, dass der funktionale Stil nur ein Ersatz für die Aussage eines armen Java-Mannes ist

employeeService
.getEmployee()
?.getId()
?.apply(id => System.out.println(id));

bekannt aus anderen Sprachen. Stimmen wir dieser Aussage zu?

  • ist nicht (wirklich) funktionsfähig?
  • ist dem alten Code sehr ähnlich, nur viel einfacher?
  • ist viel lesbarer als der funktionale Stil?
  • ist Debugger-freundlich?
maaartinus
quelle
Sie scheinen C # 's zu beschreiben Umsetzung dieses Konzepts mit einer Sprache - Funktion als völlig anders Java Implementierung mit einer Core - Bibliothek Klasse. Sie sehen für mich
genauso aus
@Caleth Auf keinen Fall. Wir könnten über das "Konzept der Vereinfachung einer nullsicheren Bewertung" sprechen, aber ich würde es nicht als "Konzept" bezeichnen. Wir können sagen, dass Java dasselbe mit einer Kernbibliotheksklasse implementiert, aber es ist nur ein Chaos. Beginnen Sie einfach damit employeeService.getEmployee().getId().apply(id => System.out.println(id));und machen Sie es einmal mit dem Operator "Sichere Navigation" und einmal mit dem Operator "Sicher" nullsicher Optional. Sicher, wir könnten es ein "Implementierungsdetail" nennen, aber die Implementierung ist wichtig. Es gibt Fälle, in denen Lamdas Code einfacher und schöner machen, aber hier ist es nur ein hässlicher Missbrauch.
Maaartinus
Bibliotheksklasse: Optional.of(employeeService).map(EmployeeService::getEmployee).map(Employee::getId).ifPresent(System.out::println);Sprache - Funktion: employeeService.getEmployee()?.getId()?.apply(id => System.out.println(id));. Zwei Syntaxen, aber sie sehen mir am Ende sehr ähnlich . Und das "Konzept" ist Optionalaka NullableakaMaybe
Caleth
Ich verstehe nicht, warum du denkst, dass einer von denen "hässlicher Missbrauch" ist und der andere gut. Ich könnte verstehen, dass Sie beide "hässlichen Missbrauch" oder beide gut finden.
Caleth
@Caleth Ein Unterschied besteht darin, dass Sie nicht zwei Fragezeichen hinzufügen, sondern alles neu schreiben müssen. Das Problem ist nicht, dass sich die Sprachfunktion und die Bibliotheksklassencodes stark unterscheiden. Das ist okay. Das Problem ist, dass der ursprüngliche Code und die Bibliotheksklassencodes sehr unterschiedlich sind. Gleiche Idee, anderer Code. Das ist sehr schade. +++Was missbraucht wird, sind Lamdas für den Umgang mit Nullability. Lamdas sind in Ordnung, ist es aber Optionalnicht. Nullability benötigt einfach eine angemessene Sprachunterstützung wie in Kotlin. Ein weiteres Problem: List<Optional<String>>hat nichts mit List<String>dem zu tun, wo wir sie brauchen würden, um verwandt zu sein.
Maaartinus
0

Optional ist ein Konzept (ein Typ höherer Ordnung) aus der funktionalen Programmierung. Durch die Verwendung entfällt die Überprüfung auf verschachtelte Nullen und Sie werden vor der Pyramide des Untergangs bewahrt.

Petras Purlys
quelle