it-swarm.com.de

Warum sollte Java 8 optional nicht in Argumenten verwendet werden?

Ich habe auf vielen Websites gelesen. Optional sollte nur als Rückgabetyp und nicht in Methodenargumenten verwendet werden. Ich habe Schwierigkeiten, einen logischen Grund dafür zu finden. Zum Beispiel habe ich eine Logik, die 2 optionale Parameter hat. Daher denke ich, dass es sinnvoll wäre, meine Methodensignatur so zu schreiben (Lösung 1):

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {
    // my logic
}

Viele Webseiten geben an, dass Optional nicht als Methodenargument verwendet werden sollte. Vor diesem Hintergrund könnte ich die folgende Methodensignatur verwenden und einen eindeutigen Javadoc-Kommentar hinzufügen, um anzugeben, dass die Argumente null sein können. Ich hoffe, dass zukünftige Betreuer den Javadoc lesen und daher vor der Verwendung der Argumente immer Nullprüfungen durchführen (Lösung 2) :

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}

Alternativ könnte ich meine Methode durch vier öffentliche Methoden ersetzen, um eine schönere Schnittstelle bereitzustellen und es deutlicher zu machen, dass p1 und p2 optional sind (Lösung 3):

public int calculateSomething() {
    calculateSomething(null, null);
}

public int calculateSomething(String p1) {
    calculateSomething(p1, null);
}

public int calculateSomething(BigDecimal p2) {
    calculateSomething(null, p2);
}

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}

Jetzt versuche ich, den Code der Klasse zu schreiben, die diese Logik für jeden Ansatz aufruft. Ich rufe zuerst die beiden Eingabeparameter von einem anderen Objekt ab, das Optionals zurückgibt, und rufe dann calculateSomething auf. Wenn Lösung 1 verwendet wird, würde der aufrufende Code daher folgendermaßen aussehen:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1, p2);

wenn Lösung 2 verwendet wird, würde der aufrufende Code folgendermaßen aussehen:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));

wenn Lösung 3 angewendet wird, könnte ich den obigen Code verwenden oder ich könnte Folgendes verwenden (es ist jedoch wesentlich mehr Code):

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result;
if (p1.isPresent()) {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p1, p2);
    } else {
        result = myObject.calculateSomething(p1);
    }
} else {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p2);
    } else {
        result = myObject.calculateSomething();
    }
}

Meine Frage ist also: Warum wird es als schlechte Praxis angesehen, Optionals als Methodenargumente zu verwenden (siehe Lösung 1)? Es scheint für mich die am besten lesbare Lösung zu sein und macht es am offensichtlichsten, dass die Parameter für zukünftige Betreuer leer/null sein könnten. (Ich bin mir bewusst, dass die Entwickler von Optional beabsichtigten, es nur als Rückgabetyp zu verwenden, aber ich kann keine logischen Gründe finden, es in diesem Szenario nicht zu verwenden.).

269
Neil Stevens

Oh, diese Codierungsstile sind mit etwas Salz zu nehmen.

  1. (+) Übergeben eines optionalen Ergebnisses an eine andere Methode ohne semantische Analyse; das der Methode zu überlassen, ist völlig in Ordnung.
  2. (-) Die Verwendung von optionalen Parametern, die eine bedingte Logik in den Methoden verursachen, ist buchstäblich kontraproduktiv.
  3. (-) Wenn Sie ein Argument in ein optionales Paket packen müssen, ist dies für den Compiler suboptimal und führt zu unnötigem Wrapping.
  4. (-) Im Vergleich zu nullwertfähigen Parametern ist Optional teurer.

Allgemein: Optional werden zwei Zustände vereinheitlicht, die aufzulösen sind. Daher besser für das Ergebnis als für die Eingabe geeignet, für die Komplexität des Datenflusses.

138
Joop Eggen

Der beste Beitrag den ich zum Thema gesehen habe, wurde von Daniel Olszewski geschrieben:

Obwohl es möglicherweise verlockend ist, die Option Optional für nicht obligatorische Methodenparameter in Betracht zu ziehen, kann eine solche Lösung im Vergleich zu anderen möglichen Alternativen verblassen. Um das Problem zu veranschaulichen, überprüfen Sie die folgende Konstruktordeklaration:

public SystemMessage(String title, String content, Optional<Attachment> attachment) {
    // assigning field values
}

Auf den ersten Blick mag es eine richtige Designentscheidung sein. Immerhin wir explizit hat den Anhangsparameter als optional markiert. Allerdings da Für den Aufruf des Konstruktors kann der Client-Code etwas ___ werden. ungeschickt.

SystemMessage withoutAttachment = new SystemMessage("title", "content", Optional.empty());
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", Optional.ofNullable(attachment));

Anstatt Klarheit zu schaffen, werden die Factory-Methoden des optionalen Klasse lenkt nur den Leser ab. Beachten Sie, dass es nur eine Option gibt Parameter, aber stellen Sie sich zwei oder drei vor. Onkel Bob auf jeden Fall. wäre nicht stolz auf einen solchen Code ????

Wenn eine Methode optionale Parameter akzeptieren kann, ist es vorzuziehen, den bewährten Ansatz zu verwenden und einen solchen Fall mithilfe der Methode zu entwerfen Überlastung. Im Beispiel der SystemMessage-Klasse die Deklaration von Zwei separate Konstruktoren sind der Verwendung von Optional überlegen.

public SystemMessage(String title, String content) {
    this(title, content, null);
}

public SystemMessage(String title, String content, Attachment attachment) {
    // assigning field values
}

Diese Änderung macht den Clientcode wesentlich einfacher und lesbarer.

SystemMessage withoutAttachment = new SystemMessage("title", "content");
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", attachment);
119
Gili

Es gibt fast keine triftigen Gründe, Optional nicht als Parameter zu verwenden. Die Argumente dagegen setzen auf Argumente der Autorität (siehe Brian Goetz - sein Argument ist, dass wir Optionale, die keine Null sind, nicht durchsetzen können) oder dass die Argumente Optional null sein können (im Wesentlichen dasselbe Argument). Natürlich kann jeder Verweis in Java null sein. Wir müssen Regeln anregen, die vom Compiler erzwungen werden, nicht den Speicher des Programmierers (was problematisch ist und nicht skaliert). 

Funktionale Programmiersprachen fördern Optional-Parameter. Eine der besten Möglichkeiten, dies zu verwenden, besteht darin, über mehrere optionale Parameter zu verfügen und liftM2 zur Verwendung einer Funktion zu verwenden, vorausgesetzt, die Parameter sind nicht leer und geben einen optionalen zurück (siehe http://www.functionaljava.org/javadoc/4.4/functionaljava) /fj/data/Option.html#liftM2-fj.F- ). Java 8 hat leider eine sehr eingeschränkte Bibliotheksunterstützung implementiert, die optional unterstützt wird.

Als Java-Programmierer sollten wir nur null verwenden, um mit älteren Bibliotheken zu interagieren.

64
Mark Perry

Dieser Hinweis ist eine Variante der Faustregel "so unspezifisch wie möglich bezüglich der Eingaben und so spezifisch wie möglich bezüglich der Ausgaben sein".

Wenn Sie über eine Methode verfügen, die einen einfachen Wert ungleich Null annimmt, können Sie sie über die Variable Optional zuordnen, sodass die reine Version hinsichtlich der Eingaben strikt unspezifischer ist. Allerdings Es gibt eine Reihe von möglichen Gründen, warum Sie dennoch ein Optional-Argument benötigen möchten:

  • sie möchten, dass Ihre Funktion in Verbindung mit einer anderen API verwendet wird, die eine Optional zurückgibt.
  • Ihre Funktion sollte etwas anderes als eine leere Optional zurückgeben, wenn der angegebene Wert leer ist
  • Du denkst, dass Optional so großartig ist, dass jeder, der deine API benutzt, dazu aufgefordert wird, etwas darüber zu erfahren ;-)
10
llogiq

Das Muster mit Optional dient dazu, das Zurückgeben vonnull zu vermeiden. Es ist immer noch möglich, null an eine Methode zu übergeben.

Obwohl diese noch nicht wirklich offiziell sind, können Sie mit JSR-308 style annotations angeben, ob Sie null-Werte für die Funktion akzeptieren oder nicht. Beachten Sie, dass Sie über die richtigen Tools verfügen müssen, um es tatsächlich zu identifizieren, und dies würde mehr statische Überprüfungen als eine durchsetzbare Laufzeitrichtlinie bieten, aber es würde helfen.

public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {}
8
Makoto

Dies erscheint mir etwas albern, aber der einzige Grund, den ich mir vorstellen kann, ist, dass Objektargumente in Methodenparametern bereits optional sind - sie können null sein. Daher ist es sinnlos, jemanden dazu zu zwingen, ein vorhandenes Objekt in ein optionales Objekt zu packen. 

Es ist jedoch vernünftig, Methoden zu verketten, die Option/Rücknahmeoptionale verwenden, z. Vielleicht eine Monade.

6
Steve B.

Meine Meinung ist, dass Optional eine Monade sein sollte und diese sind in Java nicht denkbar.

Bei der funktionalen Programmierung handelt es sich um reine und Funktionen höherer Ordnung, die ihre Argumente nur anhand ihres "Geschäftsdomänen-Typs" akzeptieren und zusammenstellen. Das Erstellen von Funktionen, die sich auf die reale Welt beziehen oder deren Berechnung gemeldet werden soll (sogenannte Nebenwirkungen), erfordert die Verwendung von Funktionen, die das automatische Auspacken der Werte aus den Monaden, die die Außenwelt repräsentieren (State, Futures, vielleicht, entweder, Schriftsteller, etc ...); Dies nennt man Heben. Man kann sich das als eine Art Trennung der Sorgen vorstellen.

Durch das Mischen dieser beiden Abstraktionsebenen wird die Lesbarkeit nicht erleichtert.

3
Eddy

Optionals sind nicht für diesen Zweck konzipiert, wie von Brian Goetz erklärt.

Sie können immer @Nullable verwenden, um anzugeben, dass ein Methodenargument null sein kann. Wenn Sie ein optionales Element verwenden, können Sie Ihre Methodenlogik nicht wirklich sauberer schreiben.

2
Kieran

Ein weiterer Grund, vorsichtig zu sein, wenn Sie eine Optional als Parameter übergeben, besteht darin, dass eine Methode etwas tun sollte. Wenn Sie einen Optional-Parameter übergeben, können Sie mehr als eine Sache bevorzugen.

public void method(Optional<MyClass> param) {
     if(param.isPresent()) {
         //do something
     } else {
         //do some other
     }
 }
2
Pau

Ich denke, das liegt daran, dass Sie normalerweise Ihre Funktionen zum Manipulieren von Daten schreiben und sie dann mit Optional und ähnlichen Funktionen auf map heben. Dies fügt das Standardverhalten von Optional hinzu . Natürlich kann es Fälle geben, in denen es notwendig ist, eine eigene Hilfsfunktion zu schreiben, die mit Optional funktioniert.

1
Danil Gaponov

Ich glaube, dass die Resonanz des Seins ist, dass Sie zuerst prüfen müssen, ob Optional selbst null ist, und dann versuchen, den Wert auszuwerten, den es umgibt. Zu viele unnötige Validierungen.

1
Macchiatow

Schauen Sie sich das JavaDoc in JDK10, https://docs.Oracle.com/javase/10/docs/api/Java/util/Optional.html an, eine API-Anmerkung wird hinzugefügt:

API-Hinweis: Optional ist in erster Linie für die Verwendung als Methodenrückgabetyp gedacht, bei dem eindeutig "kein Ergebnis" dargestellt werden muss und die Verwendung von Null wahrscheinlich Fehler verursacht.

1
Xiaolong

Das Akzeptieren von optionalen Parametern führt zu unnötigem Wrapping auf Anruferebene.

Zum Beispiel bei:

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {}

Angenommen, Sie haben zwei Nicht-Null-Zeichenfolgen (dh, die von einer anderen Methode zurückgegeben wurden):

String p1 = "p1"; 
String p2 = "p2";

Sie sind gezwungen, sie in optional einzuwickeln, auch wenn Sie wissen, dass sie nicht leer sind.

Dies wird noch schlimmer, wenn Sie mit anderen "abbildbaren" Strukturen komponieren müssen, dh. Eithers :

Either<Error, String> value = compute().right().map((s) -> calculateSomething(
< here you have to wrap the parameter in a Optional even if you know it's a 
  string >));

ref:

methoden sollten Option nicht als Parameter erwarten, dies ist fast immer eine Code-Geruch, der auf einen Verlust des Kontrollflusses vom Anrufer zu .__ hinweist. Zum Glück sollte der Anrufer die .__ überprüfen. Inhalt einer Option

ref. https://github.com/teamdigitale/digital-citizenship-functions/pull/148#discussion_r170862749

1
gpilotino

Wenn Sie Methode 3 verwenden, können Sie zunächst die letzten 14 Codezeilen wie folgt ersetzen:

int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));

Die vier Variationen, die Sie geschrieben haben, sind Bequemlichkeit Methoden. Sie sollten sie nur verwenden, wenn sie bequemer sind. Das ist auch der beste Ansatz. Auf diese Weise wird in der API sehr deutlich, welche Mitglieder erforderlich sind und welche nicht. Wenn Sie keine vier Methoden schreiben möchten, können Sie die Dinge durch Benennen Ihrer Parameter klarstellen:

public int calculateSomething(String p1OrNull, BigDecimal p2OrNull)

Auf diese Weise wird deutlich, dass Nullwerte zulässig sind.

Ihre Verwendung von p1.orElse(null) zeigt, wie ausführlich unser Code bei der Verwendung von Optional ist, weshalb ich dies vermeide. Optional wurde für die funktionale Programmierung geschrieben. Streams brauchen es. Ihre Methoden sollten wahrscheinlich niemals Optional zurückgeben, es sei denn, es ist erforderlich, sie in der funktionalen Programmierung zu verwenden. Es gibt Methoden wie die Optional.flatMap() -Methode, für die ein Verweis auf eine Funktion erforderlich ist, die Optional zurückgibt. Hier ist seine Unterschrift:

public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)

Das ist normalerweise der einzige gute Grund, eine Methode zu schreiben, die Optional zurückgibt. Aber auch dort kann es vermieden werden. Sie können einen Getter, der nicht Optional zurückgibt, an eine Methode wie flatMap () übergeben, indem Sie ihn in eine andere Methode einschließen, die die Funktion in den richtigen Typ konvertiert. Die Wrapper-Methode sieht folgendermaßen aus:

public static <T, U> Function<? super T, Optional<U>> optFun(Function<T, U> function) {
    return t -> Optional.ofNullable(function.apply(t));
}

Angenommen, Sie haben einen Getter wie diesen: String getName()

Sie können es nicht wie folgt an flatMap übergeben:

opt.flatMap(Widget::getName) // Won't work!

Aber Sie können es so übergeben:

opt.flatMap(optFun(Widget::getName)) // Works great!

Außerhalb der funktionalen Programmierung sollten Optionals vermieden werden.

Brian Goetz sagte es am besten, als er dies sagte:

Der Grund, warum Optional zu Java 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;
0
MiguelMunoz

Am Anfang habe ich es auch vorgezogen, Optionals als Parameter zu übergeben. Wenn Sie jedoch von einer API-Designer-Perspektive zu einer API-Benutzer-Perspektive wechseln, sehen Sie die Nachteile. 

Für Ihr Beispiel, in dem jeder Parameter optional ist, würde ich vorschlagen, die Berechnungsmethode wie folgt in eine eigene Klasse zu ändern: 

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

MyCalculator mc = new MyCalculator();
p1.map(mc::setP1);
p2.map(mc::setP2);
int result = mc.calculate();
0
Torsten

Ein weiterer Ansatz ist, was Sie tun können 

// get your optionals first
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

// bind values to a function
Supplier<Integer> calculatedValueSupplier = () -> { // your logic here using both optional as state}

Wenn Sie eine Funktion (in diesem Fall Lieferant) erstellt haben, können Sie diese wie jede andere Variable weitergeben und sie mit aufrufen

calculatedValueSupplier.apply();

Die Idee hier ist, ob Sie einen optionalen Wert haben oder nicht, ist das interne Detail Ihrer Funktion und wird nicht in Parametern angegeben. Das Denken von Funktionen beim Nachdenken über optionale Parameter ist eine sehr nützliche Technik, die ich gefunden habe.

Ihre Frage, ob Sie dies tatsächlich tun sollten oder nicht, hängt von Ihren Vorlieben ab, aber wie andere sagten, wird Ihre API hässlich, um es gelinde auszudrücken.

0
Swaraj Yadav

Wenn Sie Optional als Methodenargumente verwenden, muss null auch auf sich selbst geprüft werden. Unnötige doppelte Logik. Zum Beispiel:-

void doSomething(Optional<ISOCode> isoOpt, Optional<Product> prodOpt){

    if(isoOpt != null){
       isoOpt.orElse(ISOCode.UNKNOWN);
    }

    if(prodOpt != null){
       prodOpt.orElseThrow(NullPointerException::new);
    }

    //more instructions
}

Stellen Sie sich vor, die Anzahl der Optional-Parameter wächst. Wenn die Nullprüfung vergessen wird, wird möglicherweise zur Laufzeit NPE ausgegeben.

Wenn Ihre Methode public ist, wird der Client außerdem gezwungen, seine Parameter in Optionals einzugeben, was insbesondere bei einer wachsenden Anzahl von Argumenten eine Belastung darstellt.

0
Awan Biru

Dies liegt daran, dass wir unterschiedliche Anforderungen an einen API-Benutzer und einen API-Entwickler haben. 

Ein Entwickler ist für die Bereitstellung einer genauen Spezifikation und einer korrekten Implementierung verantwortlich. Wenn dem Entwickler bereits bekannt ist, dass ein Argument optional ist, muss die Implementierung korrekt damit umgehen, unabhängig davon, ob es sich um eine Null oder eine Option handelt. Die API sollte für den Benutzer so einfach wie möglich sein und Null ist die einfachste.

Auf der anderen Seite wird das Ergebnis vom API-Entwickler an den Benutzer übergeben. Auch wenn die Spezifikation vollständig und ausführlich ist, besteht immer noch die Möglichkeit, dass der Benutzer sie nicht kennt oder nur faul ist, um damit umzugehen. In diesem Fall zwingt das optionale Ergebnis den Benutzer, zusätzlichen Code zu schreiben, um ein eventuell leeres Ergebnis zu behandeln.

0
speedogoo

Ich weiß, dass es bei dieser Frage eher um Meinungen als um harte Fakten geht. Aber ich bin vor kurzem von einem .net-Entwickler zu einem Java-Entwickler gewechselt. Daher bin ich erst seit kurzem bei der Optionale Partei. Ich würde es auch vorziehen, dies als Kommentar anzugeben, aber da mein Punktestand mich nicht dazu lässt, einen Kommentar abzugeben, muss ich dies stattdessen als Antwort angeben.

Was ich gemacht habe, was mir als Faustregel gut gedient hat. Ist Optionals für Rückgabetypen zu verwenden und Optionals nur als Parameter zu verwenden, wenn ich sowohl den Wert von Optional als auch das Wetter benötige oder nicht oder dass das Optional einen Wert innerhalb der Methode hatte.

Wenn ich mich nur um den Wert kümmere, überprüfe ich vor dem Aufruf der Methode isPresent. Wenn ich eine Art Protokollierung oder eine andere Logik innerhalb der Methode habe, die davon abhängt, ob der Wert vorhanden ist, dann werde ich gerne das Optionale übergeben.

0
Blair

Obwohl dieses Thema alt ist, interessiere ich mich gerade dafür.

Persönlich bevorzuge ich Lösung 3, während die Angabe eines Arguments für eine Methode immer als nicht null gilt (ich füge gerne lomboks @NonNull hinzu). Wenn Sie also wirklich alle Ihre 4 Fälle akzeptieren möchten, warum nicht durch ein Parameterobjekt? Vielleicht kann Ihr otherObject befriedigen? Im Allgemeinen würde ich fragen, ob ich wirklich eine API bereitstellen möchte, die diese (optionalen) Werte akzeptiert, oder ob ich eine neue Architektur erstellen kann, um optionale Parameter zu vermeiden. Wenn Sie denken, dass Sie wirklich optionale Parameter benötigen, gehe ich davon aus, dass Sie SRP unterbrechen.

0