Sollten Java 8-Getter den optionalen Typ zurückgeben?


288

Optional Der in Java 8 eingeführte Typ ist für viele Entwickler neu.

Ist eine Getter-Methode, die Optional<Foo>anstelle des Klassikers Fooeinen Typ zurückgibt, eine gute Praxis? Angenommen, der Wert kann sein null.


8
Obwohl dies wahrscheinlich zu Meinungsäußerungen führen wird, ist es eine gute Frage. Ich freue mich auf eine Antwort mit echten Fakten zu diesem Thema.
Justin

8
Die Frage ist, ob die Nullfähigkeit unvermeidbar ist. Eine Komponente kann eine Eigenschaft haben, die null sein darf, aber dennoch kann der Programmierer, der diese Komponente verwendet, entscheiden, diese Eigenschaft strikt nicht zu behalten null. Der Programmierer sollte sich dann also nicht darum kümmern müssen Optional. Mit anderen Worten, es handelt sich nulltatsächlich um das Fehlen eines Werts wie beim Ergebnis einer Suche (sofern Optionalzutreffend) oder es handelt sich nullnur um ein Mitglied der Menge möglicher Werte.
Holger

1
Siehe auch die Diskussion über @NotNullAnmerkungen: stackoverflow.com/q/4963300/873282
koppor

Antworten:


515

Natürlich werden die Leute tun, was sie wollen. Aber wir hatten eine klare Absicht, als wir diese Funktion hinzufügten, und es sollte kein allgemeiner Zweck sein. Vielleicht tippen Sie so viel, wie viele Leute uns dies gewünscht hätten. Unsere Absicht war es, einen begrenzten Mechanismus für Rückgabetypen für Bibliotheksmethoden bereitzustellen, bei denen es eine eindeutige Möglichkeit geben musste, "kein Ergebnis" darzustellen, und die Verwendung nullfür solche Typen war mit überwältigender Wahrscheinlichkeit fehleranfällig.

Zum Beispiel sollten Sie es wahrscheinlich niemals für etwas verwenden, das ein Array von Ergebnissen oder eine Liste von Ergebnissen zurückgibt. Geben Sie stattdessen ein leeres Array oder eine leere Liste zurück. Sie sollten es fast nie als Feld für etwas oder als Methodenparameter verwenden.

Ich denke, die routinemäßige Verwendung als Rückgabewert für Getter wäre definitiv überbeansprucht.

Es ist nichts Falsches an Optional, dass es vermieden werden sollte, es ist einfach nicht das, was sich viele Menschen wünschen, und dementsprechend waren wir ziemlich besorgt über das Risiko einer eifrigen Überbeanspruchung.

(Ankündigung des öffentlichen Dienstes: Rufen Sie NIEMALS an , es Optional.getsei denn, Sie können nachweisen, dass es niemals null sein wird. Verwenden Sie stattdessen eine der sicheren Methoden wie orElseoder ifPresent. Im Nachhinein hätten wir so getetwas getOrElseThrowNoSuchElementExceptionoder etwas nennen sollen, das deutlich machte, dass dies eine äußerst gefährliche Methode war das hat den ganzen Zweck von Optionalin erster Linie untergraben . Lektion gelernt. (UPDATE: Java 10 hat Optional.orElseThrow(), was semantisch äquivalent ist get(), aber dessen Name angemessener ist.))


24
(In Bezug auf den letzten Teil)… und wenn wir zuversichtlich sind, dass der Wert niemals nullverwendet wird orElseThrow(AssertionError::new), ähm oder orElseThrow(NullPointerException::new)
Holger,

25
Was hätten Sie anders gemacht, wenn Sie beabsichtigt hätten , ein Allzweck-Vielleicht oder einen Typ einzuführen? Gibt es eine Möglichkeit, wie Optional nicht in die Rechnung passt, oder ist es nur so, dass die Einführung von Optionals in einer neuen API dazu führen würde, dass sie nicht Java-artig ist?
David Moles

22
Mit "Allzwecktyp" meinte ich, es in das Typensystem der Sprache einzubauen, anstatt eine Bibliotheksklasse bereitzustellen, die es annähert. (Einige Sprachen haben Typen für T? (T oder null) und T! (Nicht nullbares T)) Optional ist nur eine Klasse; Wir können keine impliziten Konvertierungen zwischen Foo und Optional <Foo> vornehmen, wie dies mit Sprachunterstützung möglich wäre.
Brian Goetz

11
Ich habe mich eine Weile gefragt, warum der Schwerpunkt in der Java-Welt auf der optionalen und nicht auf der besseren statischen Analyse lag. Optional hat einige Vorteile, aber der große Vorteil nullist die Abwärtskompatibilität. Map::getGibt eine Nullable zurück V, keine Optional<V>, und das wird sich nie ändern. Es hätte jedoch leicht kommentiert werden @Nullablekönnen. Jetzt haben wir zwei Möglichkeiten, um
Wertmangel

21
Ich hatte keine Ahnung, dass die Verwendung als Rückgabewert für eine Eigenschaft nicht das war, was Sie beabsichtigt hatten, bis ich diese Antwort hier auf StackOverflow gelesen habe. Tatsächlich wurde ich zu der Überzeugung gebracht, dass es genau das war, was Sie beabsichtigt hatten, nachdem Sie diesen Artikel auf der Oracle-Website gelesen hatten : oracle.com/technetwork/articles/java/… Vielen Dank für die Klarstellung
Jason Thompson

73

Nachdem ich selbst ein bisschen recherchiert habe, bin ich auf eine Reihe von Dingen gestoßen, die darauf hindeuten könnten, wann dies angemessen ist. Das maßgeblichste ist das folgende Zitat aus einem Oracle-Artikel:

"Es ist wichtig zu beachten, dass die Absicht der optionalen Klasse nicht darin besteht, jede einzelne Nullreferenz zu ersetzen . Stattdessen soll sie dazu beitragen, verständlichere APIs zu entwerfen, sodass Sie durch einfaches Lesen der Signatur einer Methode feststellen können, ob Sie dies tun." kann einen optionalen Wert erwarten. Dies zwingt Sie dazu, eine Option aktiv auszupacken, um das Fehlen eines Werts zu beheben. " - Keine Lust mehr auf Nullzeiger-Ausnahmen? Erwägen Sie die Verwendung von Java SE 8 Optional!

Ich habe auch diesen Auszug aus Java 8 gefunden. Optional: Verwendung

"Optional ist nicht dazu gedacht, in diesen Kontexten verwendet zu werden, da es uns nichts kauft:

  • in der Domänenmodellschicht (nicht serialisierbar)
  • in DTOs (gleicher Grund)
  • in Eingabeparametern von Methoden
  • in Konstruktorparametern "

Das scheint auch einige gültige Punkte aufzuwerfen.

Ich konnte keine negativen Konnotationen oder roten Fahnen finden, die darauf hindeuten, dass Optionaldies vermieden werden sollte. Ich denke, die allgemeine Idee ist, wenn es hilfreich ist oder die Benutzerfreundlichkeit Ihrer API verbessert, verwenden Sie es.


11
stackoverflow.com/questions/25693309 - es scheint, dass Jackson es bereits unterstützt, so dass "nicht serialisierbar" nicht mehr als gültiger Grund gilt :)
Vlasec

1
Ich schlage vor, Ihre Antwortadresse zu haben, warum die Verwendung Optionalvon Eingabeparametern von Methoden (insbesondere Konstruktoren) "uns nichts kaufen wird". dolszewski.com/java/java-8-optional-use-cases enthält eine nette Erklärung.
Gili

Ich habe festgestellt, dass für das Currying optionaler Ergebnisse, die in andere APIs umgewandelt werden müssen, ein optionaler Parameter erforderlich ist. Das Ergebnis ist eine ziemlich verständliche API. Siehe stackoverflow.com/a/31923105/105870
Karl der Heide

1
Die Verbindung ist unterbrochen. Könnten Sie bitte die Antwort aktualisieren?
Softarn

2
Das Problem ist, dass die API trotz der besten Absichten des Entwicklers häufig nicht verbessert wird. Ich habe Sammlungsbeispiele für schlechte Verwendungen von Optional erstellt , die alle aus dem Produktionscode stammen, den Sie überprüfen können.
Miguel Munoz

20

Ich würde allgemein sagen, dass es eine gute Idee ist, den optionalen Typ für Rückgabewerte zu verwenden, die nullwertfähig sein können. In Bezug auf Frameworks gehe ich jedoch davon aus, dass das Ersetzen klassischer Getter durch optionale Typen bei der Arbeit mit Frameworks (z. B. Hibernate), die auf Codierungskonventionen für Getter und Setter beruhen, große Probleme verursachen wird.


14
Dieser Rat ist genau das, was ich unter "wir sind besorgt über das Risiko eifriger Überbeanspruchung" in stackoverflow.com/a/26328555/3553087 gemeint habe .
Brian Goetz

13

Der Grund, warum OptionalJava hinzugefügt wurde, ist folgender:

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

ist sauberer als das:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;

Mein Punkt ist, dass Optional geschrieben wurde, um die funktionale Programmierung zu unterstützen , die gleichzeitig zu Java hinzugefügt wurde. (Das Beispiel stammt von einem Blog von Brian Goetz . Ein besseres Beispiel könnte die orElse()Methode verwenden, da dieser Code ohnehin eine Ausnahme auslöst, aber Sie erhalten das Bild.)

Aber jetzt verwenden die Leute Optional aus einem ganz anderen Grund. Sie verwenden es, um einen Fehler im Sprachdesign zu beheben. Der Fehler ist folgender: Es gibt keine Möglichkeit anzugeben, welche Parameter und Rückgabewerte einer API null sein dürfen. Es kann in den Javadocs erwähnt werden, aber die meisten Entwickler schreiben nicht einmal Javadocs für ihren Code, und nicht viele werden die Javadocs beim Schreiben überprüfen. Dies führt zu einer Menge Code, der immer nach Nullwerten sucht, bevor er sie verwendet, obwohl sie oft nicht null sein können, da sie bereits neun- oder zehnmal im Aufrufstapel wiederholt validiert wurden.

Ich denke, es gab einen echten Durst, diesen Fehler zu beheben, weil so viele Leute, die die neue optionale Klasse sahen, davon ausgegangen sind, dass ihr Zweck darin bestand, APIs klarer zu machen. Aus diesem Grund stellen die Leute Fragen wie "Sollten Getter Optionals zurückgeben?" Nein, das sollten sie wahrscheinlich nicht, es sei denn, Sie erwarten, dass der Getter für die funktionale Programmierung verwendet wird, was sehr unwahrscheinlich ist. Wenn Sie sich ansehen, wo Optional in der Java-API verwendet wird, sind dies hauptsächlich die Stream-Klassen, die den Kern der funktionalen Programmierung bilden. (Ich habe nicht sehr gründlich nachgesehen, aber die Stream-Klassen sind möglicherweise der einzige Ort, an dem sie verwendet werden.)

Wenn Sie vorhaben, einen Getter in einem Teil des Funktionscodes zu verwenden, ist es möglicherweise eine gute Idee, einen Standard-Getter und einen zweiten zu haben, der Optional zurückgibt.

Oh, und wenn Ihre Klasse serialisierbar sein soll, sollten Sie Optional auf keinen Fall verwenden.

Optionale Optionen sind eine sehr schlechte Lösung für den API-Fehler, da a) sie sehr ausführlich sind und b) sie dieses Problem überhaupt nicht lösen sollten.

Eine viel bessere Lösung für den API-Fehler ist der Nullness Checker . Dies ist ein Annotationsprozessor, mit dem Sie angeben können, welche Parameter und Rückgabewerte null sein dürfen, indem Sie sie mit @Nullable annotieren. Auf diese Weise kann der Compiler den Code scannen und herausfinden, ob ein Wert, der tatsächlich null sein kann, an einen Wert übergeben wird, bei dem null nicht zulässig ist. Standardmäßig wird davon ausgegangen, dass nichts null sein darf, es sei denn, es ist mit Anmerkungen versehen. Auf diese Weise müssen Sie sich keine Gedanken über Nullwerte machen. Das Übergeben eines Nullwerts an einen Parameter führt zu einem Compilerfehler. Das Testen eines Objekts auf Null, das nicht Null sein kann, erzeugt eine Compiler-Warnung. Dies hat zur Folge, dass die NullPointerException von einem Laufzeitfehler in einen Fehler zur Kompilierungszeit geändert wird.

Das ändert alles.

Verwenden Sie für Ihre Getter nicht Optional. Und versuchen Sie, Ihre Klassen so zu gestalten, dass keines der Mitglieder möglicherweise null sein kann. Und vielleicht versuchen Sie, den Nullness Checker zu Ihrem Projekt hinzuzufügen und Ihre Getter und Setter-Parameter @Nullable zu deklarieren, wenn sie benötigt werden. Ich habe das nur mit neuen Projekten gemacht. Wahrscheinlich werden in bestehenden Projekten viele Warnungen ausgegeben, die mit vielen überflüssigen Nulltests geschrieben wurden. Daher kann eine Nachrüstung schwierig sein. Aber es wird auch viele Fehler fangen. Ich liebe es. Mein Code ist dadurch viel sauberer und zuverlässiger.

(Es gibt auch eine neue Sprache, die dies behebt. Mit Kotlin, das in Java-Bytecode kompiliert wird, können Sie angeben, ob ein Objekt beim Deklarieren null sein darf. Dies ist ein sauberer Ansatz.)

Nachtrag zum Originalbeitrag (Version 2)

Nachdem ich viel darüber nachgedacht habe, bin ich widerwillig zu dem Schluss gekommen, dass es akzeptabel ist, Optional unter einer Bedingung zurückzugeben: Dass der abgerufene Wert tatsächlich null sein könnte. Ich habe viel Code gesehen, in dem Leute routinemäßig Optional von Gettern zurückgeben, die unmöglich null zurückgeben können. Ich sehe dies als eine sehr schlechte Codierungspraxis, die dem Code nur Komplexität verleiht, was Fehler wahrscheinlicher macht. Wenn der zurückgegebene Wert jedoch tatsächlich null ist, schließen Sie ihn in eine Option ein.

Beachten Sie, dass Methoden, die für die funktionale Programmierung entwickelt wurden und eine Funktionsreferenz erfordern, in zwei Formen geschrieben werden (und sollten), von denen eine optional ist. Zum Beispiel Optional.map()und Optional.flatMap()beide nehmen Funktionsreferenzen. Der erste bezieht sich auf einen gewöhnlichen Getter und der zweite auf einen, der Optional zurückgibt. Sie tun also niemandem einen Gefallen, indem Sie eine Option zurückgeben, bei der der Wert nicht null sein kann.

Trotzdem sehe ich immer noch, dass der vom Nullness Checker verwendete Ansatz der beste Weg ist, um mit Nullen umzugehen, da sie NullPointerExceptions von Laufzeitfehlern umwandeln, um Zeitfehler zu kompilieren.


2
Diese Antwort erscheint mir am angemessensten. Optional wurde nur in Java 8 hinzugefügt, wenn Streams hinzugefügt wurden. Und soweit ich gesehen habe, geben nur Stream-Funktionen optional zurück.
Archit

1
Ich denke, das ist eine der besten Antworten. Die Leute wissen, was optional ist und wie es funktioniert. Am verwirrendsten ist jedoch, wo und wie man es benutzt und wie man es benutzt. Wenn man dies liest, werden viele Zweifel ausgeräumt.
ParagFlume

3

Wenn Sie moderne Serializer und andere Frameworks verwenden, die dies verstehen, habe Optionalich festgestellt, dass diese Richtlinien beim Schreiben von EntityBeans und Domänenebenen gut funktionieren :

  1. Wenn die Serialisierungsschicht (normalerweise eine Datenbank) einen nullWert für eine Zelle in der Spalte BARin der Tabelle zulässt FOO, kann der Getter Foo.getBar()zurückgeben Optionalund dem Entwickler anzeigen, dass dieser Wert vernünftigerweise null sein kann, und er sollte dies behandeln. Wenn die Datenbank garantiert, dass der Wert nicht null ist, sollte der Getter dies nicht in ein einschließen Optional.
  2. Foo.barsollte sein privateund nicht sein Optional. Es gibt wirklich keinen Grund dafür, Optionalwenn es so ist private.
  3. Der Setter Foo.setBar(String bar)sollte den Typ annehmen barund nicht Optional . Wenn es in Ordnung ist, ein nullArgument zu verwenden, geben Sie dies im JavaDoc-Kommentar an. Wenn es nicht in Ordnung ist, nulleine IllegalArgumentExceptionoder eine geeignete Geschäftslogik zu verwenden, ist dies meiner Meinung nach angemessener.
  4. Konstruktoren benötigen keine OptionalArgumente (aus ähnlichen Gründen wie in Punkt 3). Im Allgemeinen umfassen ich nur Argumente im Konstruktor, müssen Nicht-Null in der Serialisierung Datenbank sein.

Um den oben effizienter zu machen, sollen Sie Ihre IDE - Vorlagen zur Erzeugung von Getter und entsprechenden Vorlagen bearbeiten toString(), equals(Obj o)usw. oder Verwendung Feldern direkt für diejenigen ( die meisten IDE - Generatoren bereits mit Nullen beschäftigen).

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.