Exemples d’utilisation
Maintenant que nous avons appris pourquoi les Optional
s 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 Optional
s 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 unPlayer``et retournant un ``Optional<Pet>
. Cette méthode regarde l’animal associé au joueur donnépetHelper.canSpawn()
acceptant unPet
et retournant unboolean
. Cette méthode effectue toutes les vérifications nécessaires pour s’assurer que l’animal donné peut être invoqué.petHelper.spawnPet()
acceptant unPet
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 Optional
s 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 Optional
s 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.