Supposons que vous suiviez un cours d’introduction à Java et que vous receviez des notes de 90 et 100 aux deux premiers examens. Maintenant, on vous demande quelle est votre moyenne. Une moyenne se calcule en additionnant les scores et en divisant par le nombre de scores, donc vous avez (90+100)/2. Cela donne 190/2, donc vous répondez 95. Parfait !
Maintenant, supposons que vous suiviez votre deuxième cours sur Java, et que ce soit le premier jour de cours. Nous vous demandons quelle est votre moyenne dans ce cours qui vient de commencer. Vous n’avez pas encore passé d’examens, donc vous n’avez rien à moyenner. Il ne serait pas exact de dire que votre moyenne est zéro. Cela semble mauvais et n’est pas vrai. Il n’y a tout simplement pas de données, donc vous n’avez pas de moyenne.
Comment exprimons-nous cette réponse “nous ne savons pas” ou “non applicable” en Java ? Nous utilisons le type Optional
. Un Optional
est créé en utilisant une fabrique. Vous pouvez soit demander un Optional
vide, soit passer une valeur que l’Optional
va envelopper. Pensez à un Optional
comme à une boîte qui pourrait avoir quelque chose dedans ou pourrait être vide.
Créer un Optional
Voici comment coder notre méthode de moyenne :
public static Optional<Double> moyenne(int... scores) {
if (scores.length == 0) return Optional.empty();
int somme = 0;
for (int score: scores) somme += score;
return Optional.of((double) somme / scores.length);
}
La condition de retour renvoie un Optional
vide lorsque nous ne pouvons pas calculer une moyenne. Les lignes suivantes additionnent les scores. Il existe une façon de faire ce calcul en programmation fonctionnelle, mais nous y reviendrons plus tard. En fait, toute la méthode pourrait être écrite en une ligne, mais cela ne vous apprendrait pas comment fonctionne Optional
! La dernière ligne crée un Optional
pour envelopper la moyenne.
L’appel de la méthode montre ce qui se trouve dans nos deux boîtes :
System.out.println(moyenne(90, 100)); // Optional[95.0]
System.out.println(moyenne()); // Optional.empty
Vous pouvez voir qu’un Optional
contient une valeur et l’autre est vide. Normalement, nous voulons vérifier si une valeur est présente et/ou la récupérer. Voici une façon de faire :
Optional<Double> opt = moyenne(90, 100);
if (opt.isPresent())
System.out.println(opt.get()); // 95.0
D’abord, nous vérifions si l’Optional
contient une valeur. Ensuite, nous l’imprimons. Que se passerait-il si nous ne faisions pas la vérification et que l’Optional
était vide ?
Optional<Double> opt = moyenne();
System.out.println(opt.get()); // NoSuchElementException
Nous obtiendrions une exception puisqu’il n’y a pas de valeur à l’intérieur de l’Optional
.
java.util.NoSuchElementException: No value present
Lors de la création d’un Optional
, il est courant de vouloir utiliser empty()
lorsque la valeur est null
. Vous pouvez le faire avec une instruction if
ou un opérateur ternaire. Nous utilisons l’opérateur ternaire (?:
) pour simplifier le code.
Optional o = (valeur == null) ? Optional.empty() : Optional.of(valeur);
Si valeur
est null
, o
se voit assigner l’Optional
vide. Sinon, nous enveloppons la valeur. Comme ce modèle est si courant, Java fournit une méthode de fabrique pour faire la même chose.
Optional o = Optional.ofNullable(valeur);
Cela couvre les méthodes statiques que vous devez connaître sur Optional
. Le tableau suivant résume la plupart des méthodes d’instance sur Optional
que vous devez connaître. Il y en a quelques autres qui impliquent le chaînage. Nous les couvrirons plus tard.
Méthode | Quand Optional est vide | Quand Optional contient une valeur |
---|---|---|
get() | Lève une exception | Renvoie la valeur |
ifPresent(Consumer c) | Ne fait rien | Appelle Consumer avec la valeur |
isPresent() | Renvoie false | Renvoie true |
orElse(T other) | Renvoie le paramètre other | Renvoie la valeur |
orElseGet(Supplier s) | Renvoie le résultat de l’appel du Supplier | Renvoie la valeur |
orElseThrow() | Lève NoSuchElementException | Renvoie la valeur |
orElseThrow(Supplier s) | Lève l’exception créée par l’appel du Supplier | Renvoie la valeur |
Vous avez déjà vu get()
et isPresent()
. Les autres méthodes vous permettent d’écrire du code qui utilise un Optional
en une ligne sans avoir à utiliser l’opérateur ternaire. Cela rend le code plus facile à lire. Au lieu d’utiliser une instruction if
, que nous avons utilisée pour vérifier la moyenne précédemment, nous pouvons spécifier un Consumer
à exécuter lorsqu’il y a une valeur à l’intérieur de l’Optional
. Quand il n’y en a pas, la méthode saute simplement l’exécution du Consumer
.
Optional<Double> opt = moyenne(90, 100);
opt.ifPresent(System.out::println);
L’utilisation de ifPresent()
exprime mieux notre intention. Nous voulons que quelque chose soit fait si une valeur est présente. Vous pouvez le considérer comme une instruction if
sans else
.
Gérer un Optional vide
Les méthodes restantes vous permettent de spécifier quoi faire si une valeur n’est pas présente. Il y a quelques choix. Les deux premiers vous permettent de spécifier une valeur de retour soit directement, soit en utilisant un Supplier
.
Optional<Double> opt = moyenne();
System.out.println(opt.orElse(Double.NaN));
System.out.println(opt.orElseGet(() -> Math.random()));
Cela imprime quelque chose comme :
NaN
0.49775932295380165
La première ligne montre que vous pouvez renvoyer une valeur ou une variable spécifique. Dans notre cas, nous imprimons la valeur “pas un nombre”. La deuxième ligne montre l’utilisation d’un Supplier
pour générer une valeur à l’exécution à renvoyer à la place. Je suis content que nos professeurs ne nous donnent pas une moyenne aléatoire, cependant !
Alternativement, nous pouvons faire en sorte que le code lève une exception si l’Optional
est vide.
Optional<Double> opt = moyenne();
System.out.println(opt.orElseThrow());
Cela imprime quelque chose comme :
Exception in thread "main" java.util.NoSuchElementException:
No value present
at java.base/java.util.Optional.orElseThrow(Optional.java:382)
Sans spécifier un Supplier
pour l’exception, Java lèvera une NoSuchElementException
. Alternativement, nous pouvons faire en sorte que le code lève une exception personnalisée si l’Optional
est vide. N’oubliez pas que la trace de la pile semble étrange car les lambdas sont générées plutôt que des classes nommées.
Optional<Double> opt = moyenne();
System.out.println(opt.orElseThrow(
() -> new IllegalStateException()));
Cela imprime quelque chose comme :
Exception in thread "main" java.lang.IllegalStateException
at optionals.Methods.lambda$orElse$1(Methods.java:31)
at java.base/java.util.Optional.orElseThrow(Optional.java:408)
La ligne avec la lambda montre l’utilisation d’un Supplier
pour créer une exception qui devrait être levée. Notez que nous n’écrivons pas throw new IllegalStateException()
. La méthode orElseThrow()
se charge de lever l’exception quand nous l’exécutons.
Les deux méthodes qui prennent un Supplier
ont des noms différents. Voyez-vous pourquoi ce code ne compile pas ?
System.out.println(opt.orElseGet(
() -> new IllegalStateException())); // NE COMPILE PAS
La variable opt
est un Optional<Double>
. Cela signifie que le Supplier
doit renvoyer un Double
. Puisque ce Supplier
renvoie une exception, le type ne correspond pas.
Le dernier exemple avec Optional
est vraiment facile. Qu’en pensez-vous ?
Optional<Double> opt = moyenne(90, 100);
System.out.println(opt.orElse(Double.NaN));
System.out.println(opt.orElseGet(() -> Math.random()));
System.out.println(opt.orElseThrow());
Cela imprime 95.0 trois fois. Puisque la valeur existe, il n’est pas nécessaire d’utiliser la logique “ou sinon”.
Optional est-il identique à null ?
Une alternative à Optional
est de renvoyer null
. Il y a quelques lacunes avec cette approche. L’une d’elles est qu’il n’y a pas de moyen clair d’exprimer que null
pourrait être une valeur spéciale. En revanche, renvoyer un Optional
est une déclaration claire dans l’API qu’il pourrait ne pas y avoir de valeur.
Un autre avantage d’Optional
est que vous pouvez utiliser un style de programmation fonctionnelle avec ifPresent()
et les autres méthodes plutôt que d’avoir besoin d’une instruction if
.
Enfin, vous verrez vers la fin que vous pouvez chaîner les appels Optional
.