Dans ce chapitre, nous allons aborder l'utilisation des Optional en java, qui ont été introduits dans la version 8.
Ils sont apparus pour pallier aux nombreux problèmes liées au mot-clé null (problèmes de lisibilité et de maintenabilité). Ils permettent de vérifier facilement la présence d'un élément et de réaliser des traitements sur celui-ci.
Afin de créer un Optional vide, utilisez la méthode Optional.empty.
// Création d'un Optional vide
Optional<String> myOptional = Optional.empty();
Remarque : Ne jamais créer un Optional à null, il perdrait tout son intérêt.
Afin de créer un Optional d'un élément non null, utilisez la méthode Optional.of.
String element = "Alex";
Optional<String> myOptional = Optional.of(element);
Remarque : Si la variable élément est à null, alors un NPE est levé.
Afin de créer un Optional d'un élément possiblement null, utilisez la méthode Optional.ofNullable.
String element = "Alex";
Optional<String> myOptional = Optional.ofNullable(element);
Optional<String> myOptional2 = Optional.ofNullable(null);
Remarque : Cette méthode est à préférer car elle null-safe.
Après avoir créé un Optional, nous pouvons vérifier s'il est vide ou non (valeur contenue à null) via les méthodes isEmpty (à partir de java 11) et isPresent.
// élément non null
Optional<String> myOptional = Optional.ofNullable("Alex");
System.out.println(myOptional.isEmpty()); // false
System.out.println(myOptional.isPresent()); // true
// élément null
Optional<String> myOptional2 = Optional.ofNullable(null);
System.out.println(myOptional2.isEmpty()); // true
System.out.println(myOptional2.isPresent()); // false
Remarque : Si aucun élément n'a été fourni (Optional.empty), les deux méthodes renverront les mêmes résultats que s'il était initialisé avec null.
La méthode la plus rapide (mais à éviter) est d'utiliser la méthode Optional.get. Cette méthode retourne l'élément désiré, mais lève une exception du type NoSuchElementException s'il est null. Exemple :
// élément non null
Optional<String> myOptional = Optional.ofNullable("Alex");
System.out.println(myOptional.get()); // Alex
// élément null
Optional<String> myOptional2 = Optional.ofNullable(null);
System.out.println(myOptional2.get());
// Exception in thread "main" java.util.NoSuchElementException: No value present
Il est possible de renvoyer une valeur par défaut dans le cas ou l'Optional serait vide via la méthode Optional.orElse.
// élément non null
Optional<String> myOptional = Optional.ofNullable("Alex");
System.out.println(myOptional.orElse("Claire")); // Alex
// élément null
Optional<String> myOptional2 = Optional.ofNullable(null);
System.out.println(myOptional2.orElse("Claire")); // Claire
Remarque : Il est possible de mettre null en valeur par défaut.
La méthode Optional.orElseGet permet de renvoyer une valeur par défaut dans le cas ou l'Optional serait vide. Elle prend en entrée un Supplier (ici lambda).
// élément non null
Optional<String> myOptional = Optional.ofNullable("Alex");
System.out.println(myOptional.orElseGet(() -> "Claire")); // Alex
// élément null
Optional<String> myOptional2 = Optional.ofNullable(null);
System.out.println(myOptional2.orElseGet(() -> "Claire")); // Claire
Remarque : Cette méthode permet d'optimiser le programme. En effet, si la méthode de récupération d'un élément par défaut prend un temps conséquent, avec la méthode OrElse ce temps sera obligatoirement consommé, Optional vide ou non, contrairement à la méthode orElseGet. En effet, le supplier ne sera exécuté que si l'élément est présent.
La méthode Optional.orElseThrow permet de lever une exception dans le cas d'une absence de valeur dans l'Optional :
String firstName = Optional.ofNullable(element).orElseThrow(); // NoSuchElementException;
String firstName2 = Optional.ofNullable(element)
.orElseThrow(() -> new Exception("L'element ne doit pas etre vide")); // Exception;
De la même manière que les Stream, les Optional permettent de filtrer le contenu via un prédicat. Si le contenu ne correspond pas, alors il ne sera pas conservé dans la suite des opérations de l'Optional.
Si l'Optional (ou le résultat de l'opération précédente (filtre, map) le rend) vide, le filtre ne sera pas appelé. Donc, il n'est pas possible d'obtenir en entrée du filtrage de valeur null. Les filtres sont effectués via la méthode Optional.filter :
Optional<Integer> myOptional = Optional.ofNullable(30);
System.out.println(myOptional.filter(age -> age > 20).isPresent()); // true
System.out.println(myOptional.filter(age -> age > 40).filter(age -> age < 100).isPresent()); // false
System.out.println(myOptional.filter(age -> age < 100).isPresent()); // true
Remarque: Les filtres peuvent s'enchaîner et ne modifient pas l'Optional d'origine.
De la même manière que les Stream, les Optional permettent de mapper le contenu d'un type dans un autre (ou de transformer l'élément courant) via une Function (ici un lambda).
Si l'Optional (ou le résultat de l'opération précédente (filtre, map) le rend) vide, le mapping ne sera pas appelé. Donc, il n'est pas possible d'obtenir en entrée du mapping de valeur null. Les mapping sont effectués via la méthode Optional.map. Pour cet exemple, nous avons crée une classe Person contenant un champ firstName.
class Person {
private String firstName;
public Person(String firstName) {
super();
this.firstName = firstName;
}
public String getFirstName() {
return firstName;
}
}
Exemple d'utilisation :
Optional<String> myOptional = Optional.ofNullable("Alex");
System.out.println(myOptional.map(String::toUpperCase).orElse("Claire"));
Person person = new Person("Angel");
String firstName = Optional.ofNullable(person).map(Person::getFirstName).orElse("Claire");
Remarque : Il est possible de combiner des maps et des filtres.
À la différence d'un mapping classique, la méthode Optional.flatMap récupère la valeur de l'objet et permet de renvoyer une autre instance d'Optional (vide, autre type etc).
private Optional<String> toLowerCase(String input) {
return Optional.ofNullable(input.toLowerCase()); // Création d'un nouveau Optional
}
Optional<String> myNullable = Optional.ofNullable("Alex");
System.out.println(myNullable.flatMap(this::toLowerCase)); // Optional[alex]
Il est possible de réaliser des actions uniquement si l'Optional n'est pas vide via la méthode Optional.ifPresent :
// Optional non vide
Optional<String> myNullable = Optional.ofNullable("Angel");
myNullable.ifPresent(x -> System.out.println(x)); // Angel
myNullable.ifPresent(System.out::println); // Angel
// Optional vide
Optional<String> myNullable2 = Optional.empty();
myNullable2.ifPresent(x -> System.out.println(x)); // lambda non exécutée
Introduit avec Java 9, la méthode Optional.ifPresentOrElse permet de définir une action à réaliser si l'Optional est vide et s'il ne l'est pas :
// Optional non vide
Optional<String> myNullable = Optional.ofNullable("Claire");
myNullable.ifPresentOrElse(System.out::println, () -> System.out.println("Optional vide"));
// Claire
// Optional vide
Optional<String> myNullable2 = Optional.empty();
myNullable2.ifPresentOrElse(System.out::println, () -> System.out.println("Optional vide"));
// Optional vide
Introduit dans Java 9, la méthode Optional.or permet de définir un nouvel Optional si celui d'origine est vide. Dans le cas contraire, celui d'origine est conservé :
Optional<String> myNullable = Optional.empty();
System.out.println(myNullable.or(() -> Optional.ofNullable("Claire")));
// Optional[Claire]
// Avec Java 8, les alternatives suivantes sont possibles :
// Arrays.asList("Claire", "Alexandre") : Alternative a Stream.of
// .stream() : Alternative a Stream.of
Stream.of("Claire", "Alexandre")
.filter(Objects::nonNull)
.findFirst();
// Optional[Claire]
// Ou encore avec un Supplier
Stream.<Supplier<String>>of(() -> "Claire", () -> "Alexandre")
.map(Supplier::get)
.filter(Objects::nonNull)
.findFirst();
// Optional[Claire]
Plusieurs méthodes permettent de convertir une collection en Stream via un Optional.
Avec Java 8 :
List<Integer> myList = Arrays.asList(1, 2, 3, 4, null);
Stream<Integer> myStream = Optional.ofNullable(myList)
.map(Collection::stream)
.orElseGet(Stream::empty);
Avec Java 9 et la méthode Optional.stream :
List<Integer> myList = Arrays.asList(1, 2, 3, 4, null);
Stream<Integer> myStream = Optional.ofNullable(myList)
.stream()
.flatMap(List::stream);
Un Optional peut être utilisé plusieurs fois, mais il est nécessaire de ne pas se mélanger entre les différentes instances gardées. De plus, un Optional n'est pas sérialisable, en effet, il n'implémente pas la classe java.io.Serializable.
L'Optional est un excellent outil, avec les Stream, pour mettre en oeuvre une approche de la programmation fonctionnelle et éviter les NPE.
Pour aller plus loin, cet article donne de très bonnes pratiques sur le sujet : "26 Reasons Why Using Optional Correctly Is Not Optional"
LauLem.com - Conditions Générales d'Utilisation - Informations Légales - Charte relative aux cookies - Charte sur la protection des données personnelles - A propos