it-swarm.com.de

Verwendet für Optional

Nachdem ich seit ungefähr 6 Monaten Java 8 verwende, bin ich ziemlich zufrieden mit den neuen API-Änderungen. Ein Bereich, in dem ich immer noch nicht sicher bin, wann ich Optional. Ich scheine zwischen dem Willen zu schwingen, es überall zu verwenden, kann etwas null sein, und nirgendwo überhaupt.

Es scheint viele Situationen zu geben, in denen ich es verwenden könnte, und ich bin mir nie sicher, ob es Vorteile bringt (Lesbarkeit/Null-Sicherheit) oder nur zusätzlichen Overhead verursacht.

Ich habe also ein paar Beispiele und würde mich für die Gedanken der Community interessieren, ob Optional von Vorteil ist.

1 - Als öffentlicher Methodenrückgabetyp, wenn die Methode null zurückgeben könnte:

public Optional<Foo> findFoo(String id);

2 - Als Methodenparameter, wenn der Parameter null sein darf:

public Foo doSomething(String id, Optional<Bar> barOptional);

3 - Als optionales Mitglied einer Bohne:

public class Book {

  private List<Pages> pages;
  private Optional<Index> index;

}

4 - In Collections:

Generell denke ich nicht:

List<Optional<Foo>>

fügt irgendetwas hinzu - zumal man filter() verwenden kann, um null Werte usw. zu entfernen, aber gibt es eine gute Verwendung für Optional in Sammlungen?

Irgendwelche Fälle, die ich verpasst habe?

229
Will

Der Hauptpunkt von Optional ist die Bereitstellung eines Mittels für eine Funktion, die einen Wert zurückgibt, um das Fehlen eines Rückgabewerts anzuzeigen. Siehe diese Diskussion . Auf diese Weise kann der Aufrufer eine Reihe fließender Methodenaufrufe fortsetzen.

Dies entspricht am ehesten dem Anwendungsfall # 1 in der Frage des OP. Obwohl Fehlen eines Wertes eine genauere Formulierung ist als null, da so etwas wie IntStream.findFirst konnte niemals null zurückgeben.

Für den Anwendungsfall # 2 , bei dem ein optionales Argument an eine Methode übergeben wird, könnte dies funktionieren, aber es ist ziemlich umständlich. Angenommen, Sie haben eine Methode, die eine Zeichenfolge gefolgt von einer optionalen zweiten Zeichenfolge verwendet. Das Akzeptieren eines Optional als zweites Argument würde zu folgendem Code führen:

foo("bar", Optional.of("baz"));
foo("bar", Optional.empty());

Sogar das Annehmen von Null ist schöner:

foo("bar", "baz");
foo("bar", null);

Am besten ist es wahrscheinlich, eine überladene Methode zu haben, die ein einzelnes String-Argument akzeptiert und für das zweite einen Standardwert bereitstellt:

foo("bar", "baz");
foo("bar");

Dies hat Einschränkungen, aber es ist viel schöner als die beiden oben genannten.

Use Cases # 3 und # 4 mit einem Optional in einem Klassenfeld oder in einer Datenstruktur wird als Missbrauch der API angesehen. Erstens verstößt es gegen das oben angegebene Hauptentwurfsziel von Optional. Zweitens bringt es keinen Mehrwert.

Es gibt drei Möglichkeiten, mit dem Fehlen eines Werts in einem Optional umzugehen: einen Ersatzwert bereitzustellen, eine Funktion aufzurufen, um einen Ersatzwert bereitzustellen, oder eine Ausnahme auszulösen. Wenn Sie in einem Feld speichern, tun Sie dies bei der Initialisierung oder Zuweisung. Wenn Sie einer Liste Werte hinzufügen, wie im OP erwähnt, haben Sie die zusätzliche Möglichkeit, den Wert einfach nicht hinzuzufügen, wodurch fehlende Werte "abgeflacht" werden.

Ich bin sicher, jemand könnte sich ein paar erfundene Fälle einfallen lassen, in denen er wirklich ein Optional in einem Feld oder einer Sammlung speichern möchte, aber im Allgemeinen ist es am besten, dies zu vermeiden.

182
Stuart Marks

Ich bin zu spät zum Spiel, aber für das, was es wert ist, möchte ich meine 2 Cent hinzufügen. Sie verstoßen gegen das Designziel von Optional , das durch Stuart Marks 'Antwort gut zusammengefasst wird, aber ich bin (offensichtlich) immer noch von ihrer Gültigkeit überzeugt.

Überall optional verwenden

Im Allgemeinen

Ich habe einen ganzen Blog-Beitrag über die Verwendung von Optional geschrieben, aber im Grunde kommt es darauf an:

  • entwerfen Sie Ihre Klassen so, dass nach Möglichkeit keine Wahlfreiheit besteht
  • in allen anderen Fällen sollte die Standardeinstellung Optional anstelle von null verwenden.
  • möglicherweise Ausnahmen machen für:
    • lokale Variablen
    • rückgabe von Werten und Argumenten an private Methoden
    • leistungskritische Codeblöcke (keine Vermutungen, verwenden Sie einen Profiler)

Die ersten beiden Ausnahmen können den wahrgenommenen Mehraufwand für das Ein- und Auspacken von Referenzen in Optional verringern. Sie werden so gewählt, dass eine Null niemals legal eine Grenze von einer Instanz in eine andere passieren kann.

Beachten Sie, dass dies fast nie Optionals in Sammlungen zulässt, was fast so schlimm ist wie nulls. Tu es einfach nicht. ;)

In Bezug auf Ihre Fragen

  1. Ja.
  2. Wenn Überlastung keine Option ist, ja.
  3. Wenn andere Ansätze (Unterklassen, Dekorieren, ...) keine Option sind, ja.
  4. Bitte nicht!

Vorteile

Auf diese Weise wird das Vorhandensein von nulls in Ihrer Codebasis verringert, sie werden jedoch nicht gelöscht. Aber das ist noch nicht einmal der Hauptpunkt. Es gibt andere wichtige Vorteile:

Klärt Absicht

Die Verwendung von Optional zeigt deutlich, dass die Variable optional ist. Jeder Leser Ihres Codes oder Konsument Ihrer API wird darüber hinweggeschlagen, dass möglicherweise nichts vorhanden ist und dass eine Überprüfung erforderlich ist, bevor auf den Wert zugegriffen werden kann.

Entfernt die Unsicherheit

Ohne Optional ist die Bedeutung eines null Vorkommens unklar. Es könnte eine rechtliche Vertretung eines Staates sein (siehe Map.get ) oder ein Implementierungsfehler wie eine fehlende oder fehlgeschlagene Initialisierung.

Dies ändert sich dramatisch mit der ständigen Verwendung von Optional. Hier bedeutet bereits das Auftreten von null das Vorhandensein eines Fehlers. (Wenn der Wert hätte fehlen dürfen, wäre ein Optional verwendet worden.) Dies erleichtert das Debuggen einer Nullzeigerausnahme erheblich, da die Frage nach der Bedeutung dieses null bereits beantwortet ist .

Weitere Null-Checks

Jetzt, da nichts mehr null sein kann, kann dies überall erzwungen werden. Unabhängig davon, ob Sie Anmerkungen, Zusicherungen oder einfache Prüfungen verwenden, müssen Sie nie darüber nachdenken, ob dieses Argument oder dieser Rückgabetyp null sein kann. Es kann nicht!

Nachteile

Natürlich gibt es keine Silberkugel ...

Performance

Das Einschließen von Werten (insbesondere von Grundelementen) in eine zusätzliche Instanz kann die Leistung beeinträchtigen. In engen Schleifen kann dies spürbar oder sogar noch schlimmer werden.

Beachten Sie, dass der Compiler möglicherweise die zusätzliche Referenz für kurze Lebensdauern von Optionals umgehen kann. In Java 10 Werttypen könnte die Strafe weiter reduzieren oder entfernen.

Serialisierung

Optional ist nicht serialisierbar aber ein Workaround ist nicht übermäßig kompliziert.

Invarianz

Aufgrund der Invarianz generischer Typen in Java werden bestimmte Vorgänge umständlich, wenn der tatsächliche Werttyp in ein generisches Typargument verschoben wird. Ein Beispiel ist hier (siehe "Parametrischer Polymorphismus") .

66
Nicolai

Persönlich bevorzuge ich IntelliJs Code Inspection Tool , um @NotNull und @Nullable Prüfungen, da diese hauptsächlich zur Kompilierungszeit ausgeführt werden (können einige Laufzeitprüfungen aufweisen) Dies hat einen geringeren Overhead hinsichtlich der Lesbarkeit des Codes und der Laufzeitleistung zur Folge. Es ist nicht so rigoros wie die Verwendung von Optional, jedoch sollte dieser Mangel an Rigorosität durch anständige Unit-Tests untermauert werden.

public @Nullable Foo findFoo(@NotNull String id);

public @NotNull Foo doSomething(@NotNull String id, @Nullable Bar barOptional);

public class Book {

  private List<Pages> pages;
  private @Nullable Index index;

}

List<@Nullable Foo> list = ..

Dies funktioniert mit Java 5 und es ist nicht erforderlich, Werte zu umbrechen und zu entpacken (oder Wrapper-Objekte zu erstellen).

25
Peter Lawrey

Ich denke, das Guava Optional und die Wiki-Seite machen es ganz gut:

Neben der Verbesserung der Lesbarkeit, die durch das Vergeben eines Namens mit null entsteht, ist der größte Vorteil von Optional die Idiotensicherheit. Es zwingt Sie, aktiv über den fehlenden Fall nachzudenken, wenn Sie möchten, dass Ihr Programm überhaupt kompiliert wird, da Sie das Optionsfeld aktiv auspacken und diesen Fall behandeln müssen. Null macht es störend einfach, Dinge einfach zu vergessen, und obwohl FindBugs hilft, denken wir, dass es das Problem nicht annähernd so gut angeht.

Dies ist besonders relevant, wenn Sie Werte zurückgeben, die möglicherweise "vorhanden" sind oder nicht. Es ist weitaus wahrscheinlicher, dass Sie (und andere) vergessen, dass other.method (a, b) einen Nullwert zurückgeben kann, als dass Sie wahrscheinlich vergessen, dass a null sein kann, wenn Sie other.method implementieren. Das Zurückgeben von Optional macht es für Aufrufer unmöglich, diesen Fall zu vergessen, da sie das Objekt selbst auspacken müssen, damit der Code kompiliert werden kann. Quelle: --- (Guava Wiki - Verwenden und Vermeiden von null - Was ist der Sinn? )

Optional fügt etwas Overhead hinzu, aber ich denke, sein klarer Vorteil besteht darin, dass explizit angegeben wird, dass ein Objekt möglicherweise nicht vorhanden ist, und dass dies Programmierer erzwingt komme mit der Situation klar. Es verhindert, dass jemand den geliebten != null - Scheck vergisst.

Am Beispiel von 2 halte ich es für weitaus expliziter, Code zu schreiben:

if(soundcard.isPresent()){
  System.out.println(soundcard.get());
}

als

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

Das Optional fängt für mich besser ein, dass keine Soundkarte vorhanden ist.

Meine 2 ¢ zu Ihren Punkten:

  1. public Optional<Foo> findFoo(String id); - Da bin ich mir nicht sicher. Vielleicht würde ich einen Result<Foo> Zurückgeben, der leer sein oder einen Foo enthalten könnte. Es ist ein ähnliches Konzept, aber nicht wirklich ein Optional.
  2. public Foo doSomething(String id, Optional<Bar> barOptional); - Ich würde @Nullable und eine Suche nach Fehlern vorziehen, wie in Peter Lawreys Antwort - siehe auch diese Diskussion .
  3. Ihr Buchbeispiel - Ich bin mir nicht sicher, ob ich die Option intern verwenden würde, was von der Komplexität abhängen könnte. Für die "API" eines Buches würde ich eine Optional<Index> getIndex() verwenden, um explizit anzugeben, dass das Buch möglicherweise keinen Index hat.
  4. Ich würde es nicht in Sammlungen verwenden, sondern keine Nullwerte in Sammlungen zulassen

Im Allgemeinen würde ich versuchen, die Weitergabe von nulls zu minimieren. (Einmal gebrannt ...) Ich denke, es lohnt sich, die entsprechenden Abstraktionen zu finden und den Programmiererkollegen mitzuteilen, was ein bestimmter Rückgabewert tatsächlich darstellt.

22
Behe

Von Oracle-Tutorial :

Der Zweck von Optional besteht nicht darin, jede einzelne Nullreferenz in Ihrer Codebasis zu ersetzen, sondern vielmehr dabei zu helfen, bessere APIs zu entwerfen, in denen Benutzer durch Lesen der Signatur einer Methode erkennen können, ob sie einen optionalen Wert erwarten. Optional zwingt Sie außerdem dazu, ein Optional aktiv zu entpacken, um das Fehlen eines Werts zu beheben. Infolgedessen schützen Sie Ihren Code vor unbeabsichtigten Nullzeigerausnahmen.

13
ctomek

1 - Als öffentlicher Methodenrückgabetyp, wenn die Methode null zurückgeben könnte:

Hier ist ein guter Artikel , der die Nützlichkeit von Fall 1 zeigt. Dort dieser Code

...
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String isocode = country.getIsocode();
            isocode = isocode.toUpperCase();
        }
    }
}
...

verwandelt sich in diese

String result = Optional.ofNullable(user)
  .flatMap(User::getAddress)
  .flatMap(Address::getCountry)
  .map(Country::getIsocode)
  .orElse("default");

durch Verwendung von Optional als Rückgabewert der jeweiligen Getter Methoden.

4
artifex
3
gontard

Hier ist eine interessante Verwendung (glaube ich) für ... Tests.

Ich werde eines meiner Projekte ausgiebig testen und daher Aussagen treffen. Nur gibt es Dinge, die ich überprüfen muss und andere, die ich nicht tun.

Ich erstelle daher Dinge, um sie zu behaupten, und verwende eine Behauptung, um sie zu verifizieren:

public final class NodeDescriptor<V>
{
    private final Optional<String> label;
    private final List<NodeDescriptor<V>> children;

    private NodeDescriptor(final Builder<V> builder)
    {
        label = Optional.fromNullable(builder.label);
        final ImmutableList.Builder<NodeDescriptor<V>> listBuilder
            = ImmutableList.builder();
        for (final Builder<V> element: builder.children)
            listBuilder.add(element.build());
        children = listBuilder.build();
    }

    public static <E> Builder<E> newBuilder()
    {
        return new Builder<E>();
    }

    public void verify(@Nonnull final Node<V> node)
    {
        final NodeAssert<V> nodeAssert = new NodeAssert<V>(node);
        nodeAssert.hasLabel(label);
    }

    public static final class Builder<V>
    {
        private String label;
        private final List<Builder<V>> children = Lists.newArrayList();

        private Builder()
        {
        }

        public Builder<V> withLabel(@Nonnull final String label)
        {
            this.label = Preconditions.checkNotNull(label);
            return this;
        }

        public Builder<V> withChildNode(@Nonnull final Builder<V> child)
        {
            Preconditions.checkNotNull(child);
            children.add(child);
            return this;
        }

        public NodeDescriptor<V> build()
        {
            return new NodeDescriptor<V>(this);
        }
    }
}

In der NodeAssert-Klasse mache ich Folgendes:

public final class NodeAssert<V>
    extends AbstractAssert<NodeAssert<V>, Node<V>>
{
    NodeAssert(final Node<V> actual)
    {
        super(Preconditions.checkNotNull(actual), NodeAssert.class);
    }

    private NodeAssert<V> hasLabel(final String label)
    {
        final String thisLabel = actual.getLabel();
        assertThat(thisLabel).overridingErrorMessage(
            "node's label is null! I didn't expect it to be"
        ).isNotNull();
        assertThat(thisLabel).overridingErrorMessage(
            "node's label is not what was expected!\n"
            + "Expected: '%s'\nActual  : '%s'\n", label, thisLabel
        ).isEqualTo(label);
        return this;
    }

    NodeAssert<V> hasLabel(@Nonnull final Optional<String> label)
    {
        return label.isPresent() ? hasLabel(label.get()) : this;
    }
}

Das heißt, die Behauptung wird wirklich nur ausgelöst, wenn ich das Etikett überprüfen möchte!

2
fge

Mit der Klasse Optional können Sie die Verwendung von null vermeiden und eine bessere Alternative anbieten:

  • Dies ermutigt den Entwickler, auf Vorhandensein zu prüfen, um nicht abgefangene NullPointerException zu vermeiden.

  • API wird besser dokumentiert, weil man sieht, wo die Werte zu erwarten sind, die fehlen können.

Optional bietet eine komfortable API für die weitere Arbeit mit dem Objekt: isPresent() ; get() ; orElse() ; orElseGet() ; orElseThrow() ; map() ; filter() ; flatmap() .

Darüber hinaus verwenden viele Frameworks diesen Datentyp aktiv und geben ihn von ihrer API zurück.

1
Aleksey Bykov

Ich denke nicht, dass Optional ein allgemeiner Ersatz für Methoden ist, die möglicherweise Nullwerte zurückgeben.

Die Grundidee ist: Das Fehlen eines Wertes bedeutet nicht, dass er möglicherweise in der Zukunft verfügbar ist. Es ist ein Unterschied zwischen findById (-1) und findById (67).

Die Hauptinformation von Optionals für den Anrufer ist, dass er möglicherweise nicht mit dem angegebenen Wert rechnet, dieser jedoch möglicherweise zu einem bestimmten Zeitpunkt verfügbar ist. Vielleicht verschwindet es wieder und kommt später noch einmal zurück. Es ist wie ein Ein/Aus-Schalter. Sie haben die "Option", das Licht ein- oder auszuschalten. Sie haben jedoch keine Option, wenn Sie kein Licht zum Einschalten haben.

Daher finde ich es zu unübersichtlich, überall dort Optionals einzuführen, wo zuvor möglicherweise null zurückgegeben wurde. Ich werde weiterhin null verwenden, aber nur in eingeschränkten Bereichen wie der Wurzel eines Baums, der verzögerten Initialisierung und expliziten Suchmethoden.

1
oopexpert

Scheint, dass Optional nur nützlich ist, wenn der Typ T in Optional ein primitiver Typ wie int, long, char usw. ist. Das ergibt für mich keinen Sinn, da Sie ohnehin einen null -Wert verwenden können.

Ich denke, es wurde von hier (oder von einem anderen ähnlichen Sprachkonzept) übernommen.

Nullable<T>

In C # dieses Nullable<T> wurde vor langer Zeit eingeführt, um Werttypen umzubrechen.

0
peter.petrov

Ein Optionalhat eine ähnliche Semantik wie eine nicht modifizierbare Instanz des Iterator-Entwurfsmusters :

  • es kann sich auf ein Objekt beziehen oder nicht (wie durch isPresent() ) gegeben
  • es kann dereferenziert werden (mit get() ), wenn es sich auf ein Objekt bezieht
  • es kann jedoch nicht zur nächsten Position in der Sequenz vorgerückt werden (es hat keine next() -Methode).

Überlegen Sie sich daher, ob Sie ein Optional in Kontexten zurückgeben oder übergeben möchten, in denen Sie zuvor möglicherweise die Verwendung eines Java Iterator in Betracht gezogen haben.

0
Raedwald