Optional — En finir avec NullPointerException
La NullPointerException est l'erreur la plus fréquente en Java. Optional<T> est un conteneur introduit en Java 8 pour représenter proprement une valeur qui peut être absente.
Notions théoriques
Le problème de null
// Sans Optional : risque de NullPointerException
Personnage p = dao.trouverParNom("Inconnu");
System.out.println(p.getForce()); // NullPointerException si p == null !
Avec Optional, l'absence de valeur est explicite dans le type de retour. L'appelant est forcé de gérer le cas vide.
Créer un Optional
// Valeur garantie non-null (lève NullPointerException si null)
Optional<Personnage> opt1 = Optional.of(aragorn);
// Valeur qui peut être null (retourne Optional.empty() si null)
Optional<Personnage> opt2 = Optional.ofNullable(resultatPeutEtreNull);
// Optional explicitement vide
Optional<Personnage> opt3 = Optional.empty();
Tester la présence
Optional<Personnage> opt = dao.trouverParNom("Gandalf");
if (opt.isPresent()) {
System.out.println("Trouvé : " + opt.get().getNom());
}
// Inverse de isPresent() (Java 11+)
if (opt.isEmpty()) {
System.out.println("Personnage introuvable");
}
attention
N'utilisez get() qu'après avoir vérifié isPresent(). Appeler get() sur un Optional vide lève une NoSuchElementException. En pratique, préférez toujours orElse ou ifPresent.
Extraire la valeur en sécurité
| Méthode | Comportement si vide |
|---|---|
get() | Lève NoSuchElementException |
orElse("défaut") | Retourne la valeur de remplacement |
orElseGet(() -> calcul()) | Exécute un Supplier pour calculer le défaut |
orElseThrow(RuntimeException::new) | Lève l'exception fournie |
// Valeur de remplacement fixe
String nom = opt.map(Personnage::getNom).orElse("Inconnu");
// Valeur calculée paresseusement (plus efficace si le calcul est coûteux)
Personnage defaut = opt.orElseGet(() -> new Guerrier("Anonyme", 50, 10));
// Lever une exception métier
Personnage p = opt.orElseThrow(() -> new PersonnageNotFoundException("Gandalf"));
Transformer sans extraire
Optional<Personnage> opt = dao.trouverParNom("Aragorn");
// map : transformer la valeur si présente
Optional<String> nom = opt.map(Personnage::getNom);
// filter : garder uniquement si condition vraie
Optional<Personnage> guerrier = opt.filter(p -> p instanceof Guerrier);
// ifPresent : consommer si présent (sans retour)
opt.ifPresent(p -> System.out.println("Force : " + p.getForce()));
Règles d'utilisation
info
Ne jamais stocker un Optional comme attribut de classe. Optional est conçu exclusivement comme type de retour d'une méthode de recherche.
// BIEN : Optional comme type de retour
public Optional<Personnage> trouverParNom(String nom) {
for (Personnage p : liste) {
if (p.getNom().equals(nom)) {
return Optional.of(p);
}
}
return Optional.empty();
}
// MAL : Optional comme attribut (ne pas faire)
private Optional<Personnage> personnageActif; // NON
Exemple pratique
import java.util.*;
public class PersonnageDAO {
private List<Personnage> personnages = new ArrayList<>();
public PersonnageDAO() {
personnages.add(new Guerrier("Aragorn", 150, 30));
personnages.add(new Mage("Gandalf", 100, 20, 50));
personnages.add(new Guerrier("Boromir", 130, 28));
}
// Retourne Optional — force l'appelant à gérer l'absence
public Optional<Personnage> trouverParNom(String nom) {
return personnages.stream()
.filter(p -> p.getNom().equals(nom))
.findFirst();
}
// Retourne le personnage le plus fort, ou vide si liste vide
public Optional<Personnage> lePlusFort() {
return personnages.stream()
.max(Comparator.comparingInt(Personnage::getForce));
}
}
public class Main {
public static void main(String[] args) {
PersonnageDAO dao = new PersonnageDAO();
// Cas 1 : personnage trouvé
dao.trouverParNom("Aragorn")
.ifPresent(p -> System.out.println("Trouvé : " + p.getNom()));
// Cas 2 : personnage absent avec valeur par défaut
String nom = dao.trouverParNom("Sauron")
.map(Personnage::getNom)
.orElse("Personnage inconnu");
System.out.println(nom);
// Cas 3 : le plus fort
dao.lePlusFort()
.ifPresent(p -> System.out.println("Le plus fort : " + p.getNom()));
}
}