En finir avec les NullPointerExceptions en Java grâce à Optional

Une des erreurs que la JVM doit lever le plus souvent lors de l’exécution d’un programme Java est très certainement la NullPointerException.

L’origine de cette erreur d’exécution est très simple. Lorsqu’on manipule un objet d’un type donné, on sait qu’il peut être compatible avec ce type (lui-même ou un type fils) ou null. Lorsque qu’il s’agit de null, il suffit d’accéder à une propriété (attribut ou méthode) de l’objet en question, qui en fait n’existe pas, pour engendrer cette fameuse NullPointerException. Rappelons que cette erreur d’exécution existe dans la plupart des langages. En C ou C++ cela provoque une erreur de segmentation et c’est encore plus gênant parce que cela provoque l’arrêt du processus. En Python cela conduit à AttributeError.

Utiliser null, une mauvaise pratique ?

Utiliser null en Java à toutes les sauces est considéré à peu près unanimement comme une mauvaise pratique.

En effet, se laisser la possibilité d’utiliser null n’importe où dans le code obligerait, pour avoir un code robuste, à vérifier l’existence d’un objet avant de l’utiliser. En procédant ainsi, on se retrouve vite dans des situations dans lesquelles on ne sait pas vraiment que faire lorsqu’on a affaire à un objet inexistant. Vérifier l’existence d’un objet avant de l’utiliser va aussi considérablement augmenter le nombre de lignes de code et en compromettre assez fortement la lisibilité.

Pour éviter d’utiliser null, il existe différentes approches. Certains proposent d’utiliser le Null Object Pattern qui consiste à utiliser un faux objet qui a un comportement par défaut, une sorte de mock finalement. Cette instance est alors utilisée partout là où on serait tenté d’utiliser null. D’autres proposent de n’accepter null en paramètre et de retourner null dans une API seulement quand c’est précisé dans la documentation.

Une solution radicale

Un moyen qui permettrait d’éradiquer définitivement ce problème serait que le compilateur soit capable de distinguer les valeurs qui sont optionnelles et celles qui sont systématiques grâce à un typage plus fort. Il obligerait alors à vérifier l’existence d’une valeur optionnelle avant de l’utiliser. Ce n’est pas la solution qui a été retenue en Java comme dans la très grande majorité des langages. Mais il existe des langages dans lesquels ce concept existe, et c’est le cas en Ceylon avec les types optionnels qui permettent de distinguer String et String? par exemple. Si on manipule un String?, on devra d’abord s’assurer de sa présence avant de récupérer la vraie valeur.

Malheureusement, il est peu envisageable qu’une future version de Java puisse intégrer une telle fonctionnalité, et ce pour des raisons de compatibilité ascendante. Cette fonctionnalité est intéressante si, lorsqu’on ne précise rien, la valeur ne peut pas être à null, car c’est la très grande majorité des cas. Cela voudrait dire que le code suivant ne pourrait plus compiler.

String aString = null;

Un compromis en Java

La célèbre et incontournable bibliothèque Guava, éditée par Google, propose depuis sa version 10.0 un moyen alternatif de contourner le problème de la NullPointerException. Cette solution n’apporte pas une réponse formelle, c’est-à-dire qu’il est quand même possible de parvenir à des NullPointerExceptions, mais elle apporte un moyen d’empêcher l’utilisation d’une valeur optionnelle de l’utiliser telle quelle. Il s’agit en fait d’une simple classe Optional qui encapsule éventuellement une valeur. La documentation donne plus de détails sur cette technique.

En utilisant Optional, on peut alors se donner comme convention de ne plus utiliser null. Malheureusement, on ne peut quand même pas se passer intégralement de null, par exemple à chaque interaction avec une API qui n’utilise pas ce principe. C’est le cas lorsqu’on demande à une Map un élément qui n’existe pas.

Comme je l’avais indiqué, Java 8 embarque nativement une classe équivalente. Cela permettra de standardiser l’utilisation de ce concept, et j’espère qu’il sera adopté massivement dans les nouveaux développements.

Plutôt que des mots, voici un exemple d’utilisation des Optional (de Guava).

public class UsersRegistry {
    private final Map<String, User> users = new HashMap<>();

    public void registerUser(User user) {
        users.put(user.getName(), user);
    }

    public Optional<User> getUserByName(String name) {
        return Optional.fromNullable(users.get(name));
    }
}
UsersRegistry usersRegistry = new UsersRegistry();
Optional<User> optionalUser = usersRegistry.getUserByName("Bob");
if (optionalUser.isPresent()) {
    User user = optionalUser.get();
    System.out.println(user.getBirthDate());
} else {
    System.out.println("user not found");
}

Optional propose également quelques méthodes utilitaires qui sont bien pratiques, c’est le cas par exemple de or() qui permet de retourner la valeur si elle existe ou une valeur par défaut qu’on donne en paramètre.

public static void print(Optional<String> optionalString) {
    System.out.println(optionalString.or("default"));
}

L’image d’en-tête provient de Flickr.

3 réflexions au sujet de « En finir avec les NullPointerExceptions en Java grâce à Optional »

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.