Exemples d’utilisation

Maintenant que nous avons appris pourquoi les Optionals sont utilisés, regardons ce que nous pouvons réellement faire avec eux en Java. Ces exemples de code (et Sponge) utilisent la classe java.util.Optional disponible dans Java 8.

Obtenir une Valeur encapsulée

La méthode get() va déballer un Optional et retourner la valeur encapsulée. Si aucune valeur n’est présente, appeler get() lèvera une NoSuchElementException, donc une vérification de présence doit être performée en premier.

Optional<String> opt = getOptionalString();
String wrappedString = opt.get();

Gérer l’absence de Valeurs

Le but du type Optional est de représenter une valeur qui pourrait ou ne pourrait pas être là. Par conséquent, plusieurs cas d’utilisation tournent auteur de la gestion de valeurs absentes.

Vérification de la Présence

La méthode isPresent() retourne true si une valeur est présente dans l”Optional. Il peut fournir la vérification la plus basique et est un équivalent à un null check classique.

Optional<String> opt = getOptionalString();
if (opt.isPresent()) {
    String wrappedString = opt.get();
    // more code
}

Utilisation des valeurs par défaut

Un modèle commun est de retomber à une valeur par défaut si aucune n’est présente. La méthode orElse() permet une déclaration d’une seule ligne qui va renvoyer soit la valeur actuelle dans l”Optional, soit la valeur par défaut fournie.

Au lieu de

Optional<String> optionalString = optionalString();
String someString;
if (optionalString.isPresent()) {
    someString = optionalString.get();
} else {
    someString = DEFAULT_STRING;
}

utilisez simplement

String someString = getOptionalString().orElse(DEFAULT_STRING);

Dans certains cas, une valeur par défaut doit être calculée d’une manière qui a des effets secondaires ou particulièrement coûteuse. Dans ce cas, il convient de calculer la valeur par défaut uniquement si c’est nécessaire (lazy evaluation). La méthode orElseGet() accepte un Supplier plutôt qu’une valeur pré-calculée. Si aucune valeur n’est présente dans le Optional lui-même, le Supplier sera appelé. Puisque Supplier est une interface contionnelle, une expression lambda ou une référence de méthode peut être passée à la place.

Au lieu de

Optional<String> optionalString = optionalString();
String someString;
if (optionalString.isPresent()) {
    someString = optionalString.get();
} else {
    someString = myPlugin.defaultString();
}

utilisez simplement

String someString = getOptionalString().orElseGet(myPlugin::defaultString);

Échouer lors de Valeurs absentes

Si une valeur absente doit conduire à une exception, il est quasiment toujours meilleur de lever une exception personnalisée au lieu de dépendre du NoSuchElementException par défaut. Si vous appelez la méthode orElseThrow() avec un Supplier, il va retourner la valeur enveloppée si elle est présente, ou lever un Throwable obtenu depuis le Supplier si l”Optional est vide. Encore une fois, puisque Supplier est une interface fonctionnelle, les expressions lambda ou les méthodes références peuvent être utilisées à la place.

Au lieu de

Optional<String> optionalString = optionalString();
if (!optionalString.isPresent()) {
    throw new MyException();
}
String someString = optionalString.get();

utilisez simplement

String someString = getOptionalString().orElseThrow(MyException::new);

Note

Si le Throwable fournit par le supplier est une exception vérifiée, il devrait également être inclus dans la signature de la fonction environnante (par exemple public void doStuff() throws MyException)

Exécution de Code Conditionnel

Si aucune valeur par défaut ne peut être utilisée, le code qui repose sur une valeur étant présente ne peut être exécuté. Tandis que ceci pourrait être traité dans une simple condition, il y a d’autres méthodes pratiques.

Valeurs Consommantes

Si votre logique permettant de gérer la valeur actuelle est déjà encapsulée dans un Consumer ou une fonction d’un paramètre, la méthode ifPresent() va accepter le consumer (ou une référence de méthode). Si la valeur est présente dans l”Optional, elle sera passée au consumer. Si le Optional est vide, rien ne se passera.

Au lieu de

Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
    myPlugin.doSomethingWithString(optionalString.get());
}

utilisez simplement

Optional<String> optionalString = getOptionalString();
optionalString.ifPresent(s -> myPlugin.doSomethingWithString(s));

ou

getOptionalString().ifPresent(myPlugin::doSomethingWithString);

Filtrage

Il est également possible de passer un Predicate. Seules les valeurs avec lesquelles ce Predicate retourne vrai seront conservées. Si aucune valeur n’est présente ou si le Predicate retourne false, un Optional vide sera retourné. Puisque cette méthode retourne un optional, elle permet de faire une chaîne avec les autres méthodes.

Au lieu de

Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
    String someString = optionalString.get();
    if (stringTester.isPalindromic(someString)) {
        myPlugin.doSomethingWithString(someString);
    }
}

utilisez simplement

getOptionalString()
      .filter(stringTester::isPalindromic)
      .ifPresent(myPlugin::doSomethingWithString);

Note

Ni cette fonction de filtrage, ni les fonctions de mapping décrites ci-dessous ne modifient l’instance sur laquelle ils sont appelés. Les Optionals sont toujours immuables.

Mapping

Une autre opération de chaînage consiste à mapper la valeur potentielle en une valeur différente. Si aucune valeur n’est présente, rien ne va changer. Mais si elle est présente, la méthode map() va retourner un Optional de la valeur retournée par la Function fournie (ou un Optional vide si cette valeur de retour est null).

Au lieu de

Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
    String someString = optionalString.toLowerCase();
    myPlugin.doSomethingWithString(someString);
}

utilisez simplement

getOptionalString()
      .map(s -> s.toLowerCase())
      .ifPresent(myPlugin::doSomethingWithString);

Astuce

Si votre fonction de mapping retourne déjà un Optional, utilisez la méthode flatMap() à la place. Elle se comportera comme map(), sauf qu’elle attend que la fonction de mapping retourne déjà un Optional et ne va donc pas emballer le résultat.

Exemples Combinés

Imaginez un plugin qui permet à chaque joueur d’avoir un animal de compagnie qui le suit. Supposons l’existence des méthodes suivantes :

  • petRegistry.getPetForPlayer() acceptant un Player``et retournant un ``Optional<Pet>. Cette méthode regarde l’animal associé au joueur donné

  • petHelper.canSpawn() acceptant un Pet et retournant un boolean. Cette méthode effectue toutes les vérifications nécessaires pour s’assurer que l’animal donné peut être invoqué.

  • petHelper.spawnPet() acceptant un Pet et ne retournant rien. Cette méthode va faire apparaître un animal pas encore apparu.

Maintenant depuis quelque part (probablement l’exécution d’une commande) nous avons notre variable optionalPlayer contenant un Optional<Player>. Nous voulons maintenant obtenir l’animal de compagnie de ce joueur, vérifier si l’animal est déjà apparu et si il ne l’est pas, le faire apparaître tout en effectuant les vérifications nécessaires pour savoir si tous les Optional contiennent une valeur. Le code utilisant seulement les méthodes basiques isPresent() et get() devient mauvais vraiment rapidement.

if (optionalPlayer.isPresent()) {
    Player player = optionalPlayer.get();
    Optional<Pet> optionalPet = petRegistry.getPetForPlayer(player);
    if (optionalPet.isPresent()) {
        Pet pet = optionalPet.get();
        if (petHelper.canSpawn(pet)) {
            petHelper.spawnPet(pet);
        }
    }
}

Toutefois à travers l’utilisation des méthodes des Optionals pour l’exécution de code conditionnelle, toutes ces vérifications de présence sont cachées, réduisant le code de référence et les niveaux d’identation et laissant ainsi le code beaucoup plus lisible :

optionalPlayer
      .flatMap(petRegistry::getPetForPlayer)
      .filter(petHelper::canSpawn)
      .ifPresent(petHelper::spawnPet);

Créer des Optionals

Si vous choisissez de fournir une API suivant le même contrat d’utiliser les Optional au lieu de retourner des valeurs null, vous derez créer des Optionals pour pouvoir les retourner. Cela se fait en appelant l’une des trois méthodes de constructeur statiques.

Optional.empty() retournera toujours un Optional vide.

Optional.of() retournera un optional emballant la valeur donnée et déclenchera un NullPointerException si la valeur était null.

Optional.ofNullable() va retourner un Optional vide si la valeur fournie est null, sinon elle retournera un Optional enveloppant la valeur.