web-dev-qa-db-fra.com

Utilise pour facultatif

Ayant utilisé Java 8 depuis maintenant plus de 6 mois, je suis plutôt satisfait des modifications apportées à l'API. Un domaine dans lequel je ne suis toujours pas sûr est quand utiliser Optional. Je semble osciller entre vouloir l’utiliser partout, quelque chose peut être null et nulle part.

Il semble y avoir beaucoup de situations dans lesquelles je pourrais l'utiliser, et je ne suis jamais sûr que cela ajoute des avantages (lisibilité/sécurité nulle) ou cause simplement une surcharge.

J'ai donc quelques exemples et je serais intéressé par les réflexions de la communauté sur le fait que Optional soit bénéfique.

1 - En tant que type de retour de méthode publique lorsque la méthode pouvait renvoyer null:

public Optional<Foo> findFoo(String id);

2 - En tant que paramètre de méthode lorsque le paramètre peut être null:

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

3 - En tant que membre optionnel d'un haricot:

public class Book {

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

}

4 - Dans Collections:

En général je ne pense pas:

List<Optional<Foo>>

ajoute quoi que ce soit - surtout que l'on peut utiliser filter() pour supprimer null valeurs, etc., mais existe-t-il de bonnes utilisations de Optional dans les collections?

Des cas que j'ai manqués?

229
Will

Le point principal de Optional est de fournir un moyen à une fonction renvoyant une valeur pour indiquer l'absence d'une valeur de retour. Voir cette discussion . Cela permet à l'appelant de continuer une chaîne d'appels de méthode fluide.

Ceci correspond le mieux au cas d'utilisation # 1 dans la question du PO. Bien que, , l'absence d'une valeur soit une formulation plus précise que null , car quelque chose comme IntStream.findFirst ne pourrait jamais renvoyer null.

Pour le cas d'utilisation # 2 , en passant un argument optionnel à une méthode, cela pourrait fonctionner, mais c'est plutôt maladroit. Supposons que votre méthode prenne une chaîne suivie d'une seconde chaîne facultative. Accepter un Optional comme deuxième argument donnerait le code suivant:

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

Même accepter null est plus agréable:

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

Le mieux est probablement d'avoir une méthode surchargée qui accepte un seul argument de chaîne et fournit une valeur par défaut pour la seconde:

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

Cela a des limites, mais c'est beaucoup plus sympa que ce qui est mentionné ci-dessus.

Cas d'utilisation # 3 et # 4 , ayant une Optional dans un champ de classe ou dans une structure de données, est considéré comme une utilisation abusive de l'API. Tout d'abord, cela va à l'encontre de l'objectif de conception principal de Optional, comme indiqué au sommet. Deuxièmement, cela n’ajoute aucune valeur.

Il existe trois façons de traiter l'absence de valeur dans un Optional: pour fournir une valeur de remplacement, appeler une fonction pour fournir une valeur de remplacement ou renvoyer une exception. Si vous stockez dans un champ, vous le feriez au moment de l'initialisation ou de l'affectation. Si vous ajoutez des valeurs à une liste, comme l'OP l'a mentionné, vous avez le choix supplémentaire de ne pas simplement ajouter de valeur, ce qui "met à plat" les valeurs absentes.

Je suis sûr que quelqu'un pourrait proposer des cas artificiels dans lesquels il souhaite vraiment stocker un Optional dans un champ ou une collection, mais en général, il est préférable d'éviter de le faire.

182
Stuart Marks

Je suis en retard pour le jeu mais pour ce que ça vaut, je veux ajouter mes 2 cents. Ils vont à l'encontre de objectif de conception de Optional , qui est bien résumé par réponse de Stuart Marks , mais je suis toujours convaincu de leur validité (évidemment).

Utilisez facultatif partout

En général

J'ai écrit tout un article de blog sur l'utilisation de Optional mais cela revient essentiellement à ceci:

  • concevez vos cours de manière à éviter les options si possible
  • dans tous les cas restants, la valeur par défaut devrait être d'utiliser Optional au lieu de null
  • faire éventuellement des exceptions pour:
    • variables locales
    • renvoie des valeurs et des arguments à des méthodes privées
    • blocs de code critiques pour la performance (pas de suppositions, utilisez un profileur)

Les deux premières exceptions permettent de réduire la surcharge perçue liée à l’emballage et au déballage des références dans Optional. Ils sont choisis de sorte qu'un null ne puisse jamais légalement passer une frontière d'une instance à une autre.

Notez que cela n'autorisera presque jamais Optionals dans les collections, ce qui est presque aussi mauvais que nulls. Juste ne le fais pas. ;)

Concernant vos questions

  1. Oui.
  2. Si la surcharge n'est pas une option, oui.
  3. Si d'autres approches (sous-classe, décoration, ...) ne sont pas une option, oui.
  4. Je t'en prie, non!

Les avantages

Cela réduit la présence de nulls dans votre base de code, bien que cela ne les élimine pas. Mais ce n’est même pas l’essentiel. Il y a d'autres avantages importants:

Clarifie l'intention

Utiliser Optional indique clairement que la variable est bien optionnelle. Tout lecteur de votre code ou consommateur de votre API sera dépassé par le fait qu'il pourrait ne rien y avoir et qu'un contrôle est nécessaire avant d'accéder à la valeur.

Élimine l'incertitude

Sans Optional le sens d'une occurrence null n'est pas clair. Il peut s'agir d'une représentation légale d'un état (voir Map.get ) ou d'une erreur d'implémentation telle qu'une initialisation manquante ou échouée.

Cela change radicalement avec l'utilisation persistante de Optional. Ici déjà, l'occurrence de null signifie la présence d'un bogue. (Parce que si la valeur était autorisée à manquer, une Optional aurait été utilisée.) Cela facilite beaucoup le débogage d'une exception de pointeur nul, car la question de la signification de cette null a déjà été résolue.

Plus de contrôles nuls

Maintenant que rien ne peut plus être null, ceci peut être appliqué partout. Que ce soit avec des annotations, des assertions ou des vérifications simples, vous ne devez jamais vous demander si cet argument ou ce type de retour peut être null. Ça ne peut pas!

Désavantages

Bien sûr, il n'y a pas de solution miracle ...

Performance

Le regroupement des valeurs (en particulier des primitives) dans une instance supplémentaire peut dégrader les performances. Dans les boucles serrées, cela pourrait devenir perceptible ou même pire.

Notez que le compilateur pourrait peut-être contourner la référence supplémentaire pour les durées de vie courtes de Optionals. Dans Java 10 types de valeur peut encore réduire ou supprimer la pénalité.

La sérialisation

Optional n'est pas sérialisable mais un solution de contournement n'est pas trop compliqué.

Invariance

En raison de l'invariance des types génériques en Java, certaines opérations deviennent lourdes lorsque le type de valeur réel est poussé dans un argument de type générique. Un exemple est donné ici (voir "Polymorphisme paramétrique") .

66
Nicolai

Personnellement, je préfère utiliser outil d’inspection de code d’IntelliJ pour utiliser les contrôles @NotNull et @Nullable, car il s’agit en grande partie du temps de compilation (peut avoir certaines vérifications d’exécution). de la lisibilité du code et des performances d'exécution. Il n’est pas aussi rigoureux que d’utiliser Optionnel, mais ce manque de rigueur devrait être étayé par des tests unitaires décents.

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 = ..

Cela fonctionne avec Java 5 et il n'est pas nécessaire d'encapsuler et de décompresser les valeurs. (ou créer des objets wrapper)

25
Peter Lawrey

Je pense que Guava Facultatif et leur page wiki le disent très bien:

Outre l’augmentation de la lisibilité qui découle de l’attribution d’un nom à null, le principal avantage de Optional est sa sécurité à toute épreuve. Cela vous oblige à réfléchir activement au cas absent si vous voulez que votre programme soit compilé, car vous devez dérouler activement l'option et traiter ce cas. Null rend tout simplement facile d'oublier des choses, et bien que FindBugs aide, nous ne pensons pas que cela règle autant le problème.

Ceci est particulièrement pertinent lorsque vous renvoyez des valeurs qui peuvent ou non être "présentes". Vous (et les autres) êtes beaucoup plus susceptible d'oublier que other.method (a, b) peut renvoyer une valeur nulle plutôt que vous risquez d'oublier que a peut être nul lorsque vous implémentez other.method. Le renvoi facultatif empêche les appelants d'oublier ce cas, car ils doivent décompresser eux-mêmes l'objet pour que leur code soit compilé. Source: --- (Guava Wiki - Utiliser et éviter les null - Quel est le but? )

Optional ajoute un surcoût, mais je pense que son avantage évident est de rendre explicite qu'un objet peut être absent et que les programmeurs gèrent la situation. Cela évite que quelqu'un oublie le chèque != null bien-aimé.

Prenons l'exemple de 2 , je pense que l'écriture de code est beaucoup plus explicite:

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

que

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

Pour moi, la Optional capture mieux le fait qu’il n’ya pas de carte son.

Mes 2 ¢ sur vos points:

  1. public Optional<Foo> findFoo(String id); - Je ne suis pas sûr de cela. Peut-être que je retournerais un Result<Foo> qui pourrait être vide ou contenir un Foo. C'est un concept similaire, mais pas vraiment un Optional.
  2. public Foo doSomething(String id, Optional<Bar> barOptional); - Je préférerais @Nullable et un contrôle findbugs, comme dans réponse de Peter Lawrey - voir aussi cette discussion .
  3. Votre exemple de livre - Je ne sais pas si j'utiliserais facultatif en interne, cela pourrait dépendre de la complexité. Pour "l'API" d'un livre, j'utiliserais une Optional<Index> getIndex() pour indiquer explicitement que le livre pourrait ne pas avoir d'index.
  4. Je ne l'utiliserais pas dans les collections, mais n'autoriserais pas les valeurs NULL dans les collections

En général, j'essayerais de minimiser les risques de contournement de nulls. (Une fois brûlé ...) Je pense qu'il est utile de trouver les abstractions appropriées et d'indiquer aux autres programmeurs ce qu'une valeur de retour représente réellement.

22
Behe

De tutoriel Oracle :

Optional n'a pas pour objectif de remplacer chaque référence null dans votre base de code, mais plutôt de contribuer à la conception de meilleures API dans lesquelles, en lisant simplement la signature d'une méthode, les utilisateurs peuvent indiquer si une valeur optionnelle est attendue. De plus, Optional vous oblige à dérouler activement une option pour gérer l'absence de valeur. Par conséquent, vous protégez votre code contre les exceptions de pointeur null non souhaitées.

13
ctomek

1 - En tant que type de retour de méthode publique lorsque la méthode pouvait renvoyer null:

Voici un bon article qui montre l'utilité de la casse d'utilisateur n ° 1. Là ce 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();
        }
    }
}
...

est transformé à cette

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

en utilisant facultatif comme valeur de retour des méthodes respectives getter .

4
artifex
3
gontard

Voici une utilisation intéressante (je crois) pour ... Tests.

J'ai l'intention de tester lourdement l'un de mes projets et donc de construire des assertions; seulement il y a des choses que je dois vérifier et d'autres que je ne vérifie pas.

Je construis donc des choses à affirmer et utilise un assert pour les vérifier, comme ceci:

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);
        }
    }
}

Dans la classe NodeAssert, je fais ceci:

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;
    }
}

Ce qui signifie que l'assertion ne se déclenche vraiment que si je veux vérifier l'étiquette!

2
fge

La classe Optional vous évite d'utiliser null et offre une meilleure alternative:

  • Ceci encourage le développeur à vérifier sa présence afin d'éviter les NullPointerException non capturés.

  • L'API devient mieux documentée car il est possible de voir, où s'attendre les valeurs qui peuvent être absentes.

Optional fournit une API pratique pour la poursuite du travail avec l'objet: isPresent() ; get() ; orElse() ; orElseGet() ; orElseThrow() ; map() ; filter() ; flatmap() .

En outre, de nombreux frameworks utilisent activement ce type de données et le renvoient depuis leur API.

1
Aleksey Bykov

Je ne pense pas que Optional soit un substitut général aux méthodes susceptibles de renvoyer des valeurs nulles.

L'idée de base est la suivante: L'absence de valeur ne signifie pas qu'elle est potentiellement disponible dans le futur. C'est une différence entre findById (-1) et findById (67).

L’information principale de Optionals pour l’appelant est qu’il ne peut pas compter sur la valeur donnée, mais que celle-ci peut être disponible à un moment donné. Peut-être qu'il disparaîtra à nouveau et reviendra plus tard une fois de plus. C'est comme un interrupteur marche/arrêt. Vous avez la "possibilité" d'allumer ou d'éteindre la lumière. Mais vous n’avez pas d’option si vous n’avez pas de lumière à allumer.

Je trouve donc trop compliqué d’introduire des options dans tous les endroits où la valeur null avait été renvoyée. Je continuerai à utiliser null, mais uniquement dans des zones restreintes telles que la racine d'un arbre, une initialisation lente et des méthodes de recherche explicites.

1
oopexpert

Semble Optional n’est utile que si le type T dans Optional est un type primitif tel que int, long, char, etc. Pour les classes "réelles", sens pour moi que vous pouvez utiliser une valeur null de toute façon.

Je pense que cela a été pris d'ici (ou d'un autre concept de langage similaire).

Nullable<T>

En C #, ce _Nullable<T>_ a été introduit il y a longtemps pour encapsuler les types de valeur.

0
peter.petrov

Un Optionala une sémantique similaire à une instance non modifiable du modèle de conception Iterator :

  • il peut ou peut ne pas faire référence à un objet (tel que donné par isPresent() )
  • il peut être déréférencé (en utilisant get() ) s'il fait référence à un objet
  • mais il ne peut pas être avancé à la position suivante de la séquence (il n’a pas de méthode next()).

Par conséquent, envisagez de renvoyer ou de transmettre un Optional dans des contextes où vous auriez peut-être déjà envisagé d'utiliser un Java Iterator .

0
Raedwald