Récupérer une valeur sans avoir à vérifier null en Java

15

Plusieurs fois, je me retrouve à vérifier null lors de la récupération d'une valeur dans une hiérarchie de données pour éviter les NullPointerExceptions, que je trouve sujettes à des erreurs et qui nécessitent beaucoup de passe-partout.

J'ai écrit une routine très simple qui me permet d'ignorer la vérification nulle lors de la récupération d'un objet ...

public final class NoNPE {

    public static <T> T get(NoNPEInterface<T> in) {
        try {
            return in.get();
        } catch (NullPointerException e) {
            return null;
        }
    }

    public interface NoNPEInterface<T> {
        T get();
    }
}

Je l'utilise un peu comme ça ...

Room room = NoNPE.get(() -> country.getTown().getHouses().get(0).getLivingRoom());

Ce qui précède m'a permis d'obtenir un objet Room ou un null, sans avoir à vérifier tous les niveaux parent.

Que pensez-vous de ce qui précède? Suis-je en train de créer un motif problématique? Existe-t-il une meilleure façon de procéder à votre avis?

Eurig Jones
la source
1
Comme vous utilisez apparemment Java 8, puis-je vous suggérer d'envisager de reconcevoir votre application pour l'utiliser java.util.Optionalau lieu de null pour représenter les données manquantes? Cela fournit des utilitaires pratiques à la fois pour le cas que vous décrivez et les cas où vous souhaitez continuer avec des données par défaut plutôt que de simplement renvoyer une condition d'échec à la fin de la chaîne.
Periata Breatta
Je pense que vous avez essentiellement redécouvert la Option(ou Maybe) monade :)
Andres F.
Il peut être possible de retourner Optional au lieu de T ou null: de cette façon, vous pouvez utiliser directement la méthode orElse (). 18 mois plus tard, mais pourrait aider quelqu'un.
Benj
Une autre approche est mentionnée dans cet article illegalargumentexception.blogspot.com/2015/03/… , l'une d'elles utilise une bibliothèque nommée kludje qui a une syntaxe très intéressante
Benj

Réponses:

13

Votre solution est très intelligente. Le problème que je vois est le fait que vous ne savez pas pourquoi vous en avez un null? Est-ce parce que la maison n'avait pas de pièces? Était-ce parce que la ville n'avait pas de maisons? Est-ce parce que le pays n'a pas de villes? Était-ce parce qu'il y avait un nulldans la position 0 de la collection à cause d'une erreur même quand il y a des maisons dans les positions 1 et plus?

Si vous utilisez largement la NonPEclasse, vous aurez de sérieux problèmes de débogage. Je pense qu'il vaut mieux savoir où exactement la chaîne est brisée que d'en obtenir silencieusement une nullqui pourrait cacher une erreur plus profonde.

Cela viole également la loi de Déméter : country.getTown().getHouses().get(0).getLivingRoom(). Plus souvent qu'autrement, la violation d'un bon principe vous oblige à mettre en œuvre des solutions peu orthodoxes pour résoudre le problème causé par la violation de ce principe.

Ma recommandation est que vous l'utilisiez avec prudence et essayez de résoudre le défaut de conception qui vous oblige à encourir l' antipattern de l'épave du train (vous n'avez donc pas à l'utiliser NonPEpartout). Sinon, vous pourriez avoir des bogues difficiles à détecter.

Tulains Córdova
la source
Très bonne réponse. Oui, je ne saurai pas où j'ai obtenu le zéro dans la chaîne. Dans de nombreux cas, je m'en fiche et ne pas avoir à vérifier la valeur nulle signifie que le code est plus lisible et moins sujet aux erreurs passe-partout. Mais oui, vous avez raison dans certains cas où j'ai besoin de prendre une décision logique différente si un objet parent est nul, cela entraînerait un problème. La méthode conventionnelle ou la classe facultative pourrait être une solution plus sûre.
Eurig Jones du
En général, lorsque vous utilisez la Optionmonade, vous ne vous souciez pas de savoir où se trouve la valeur absente dans la chaîne. Lorsque vous vous en souciez, vous utiliserez probablement un type différent, tel que Either.
Andres F.
L'approche du PO est similaire aux C # 6 ?.et aux ?[]opérateurs. Un exemple de cas où vous voudrez peut-être utiliser une telle chose est les paramètres côté serveur hiérarchiques. var shouldDoThing = settings?.a?.b?.c ?? defaultSetting;Peu importe pourquoi une partie de cela était nulle? Vous n'avez peut-être pas pu récupérer les paramètres. Vous avez peut-être décidé de supprimer une section des paramètres. Dans tous les cas, vous ne pouvez jamais vraiment compter sur l'obtention des paramètres du serveur, donc une valeur par défaut est généralement une bonne idée, et vous ne vous soucierez probablement pas pourquoi vous ne pouvez pas obtenir le paramètre réel, sauf si cela se produit très souvent alors qu'il ne devrait pas .
chris
Maintenant, je ne dis pas que c'est strictement mieux ou pire que de localiser les valeurs par défaut et de simplement récupérer la valeur que vous souhaitez via les accès normaux settings.a.b.c. Là encore, il s'agit d'un seul exemple isolé.
chris
10

L'idée est bonne, vraiment bonne en fait. Depuis Java 8, les Optionaltypes existent, une explication détaillée peut être trouvée dans Java Optional type . Un exemple avec ce que vous avez publié est

Optional.ofNullable(country)
    .map(Country::getTown)
    .map(Town::Houses);

Et plus loin.

J. Pichardo
la source
1
Oui, j'étais au courant de la classe Optional, à la fois de Java 8 et de Guava et elles sont vraiment utiles. Mais vous ne pouvez pas simplement récupérer un objet comme vous le feriez normalement pour rendre le code un peu plus difficile à lire et un peu moins performant aussi. Mais l'avantage est qu'il existe de nombreux opérateurs très utiles fournis par la classe Optional.
Eurig Jones du
3
@EurigJones Je ne pense pas que le code devienne moins performant. Lisibilité dans l'œil du spectateur, mais je dirais que Optionalc'est la solution la plus lisible des deux, ne serait-ce que parce que - contrairement à votre proposition - c'est un idiome très courant . Il est encore plus concis que le vôtre!
Andres F.3
0

Votre méthode fonctionne assez bien pour son objectif, bien que renvoyer nulls lorsque vous obtenez un NullPointerExceptionson de mauvaise conception.

Essayez d'éviter les nulls lorsque vous le pouvez et ne les passez que lorsqu'ils représentent quelque chose ou ont une signification particulière et ne les renvoyez que lorsqu'ils représentent / signifient quelque chose - sinon vous devriez jeter a NullPointerException. Cela évite les bugs et la confusion. Si un Objectne devrait pas l'être null, un NullPointerdevrait être jeté. Si un objet peut l'être, nullrien ne se passera mal lors de sa transmission. Sinon, votre méthode ci-dessus fonctionne.

Luke Melaia
la source
0

Je peux ressentir votre douleur, mais la solution proposée est une mauvaise idée.

  • Si l'un des getters lance un NPE pour une autre raison, vous l'ignorerez.
  • Il y a un risque que cette lambda intérieure devienne un code horrible. Par exemple, s'il y a une nouvelle exigence de retourner une constante spéciale lorsqu'il n'y a pas de maisons dans la ville, un programmeur paresseux peut étendre la lamda, laissant tout enveloppé NoNPE.get.
  • Comme déjà mentionné, Optional.mapc'est ce que vous recherchez.
  • La pénalité de la création d'une nouvelle instance de NullPointerException est souvent importante. Cela prend plusieurs microsecondes, d'autant plus que votre pile d'appels s'agrandit. Il est difficile de prédire où votre utilitaire finira par être utilisé.

En remarque, NoNPEInterfaceest un doublon de java.util.function.Supplier.

Dans certains cas, vous pourriez envisager d'utiliser une expression d'évaluation utils qui sont présents dans de nombreux cadres (par exemple: EL, SpEL):

evaluateProperty(country, "town.houses[0].livingRoom")
Mateusz Stefek
la source
Ok pour les modèles de pages Web, mais généralement lents à développer (pas de vérification du temps de compilation) et lents à exécuter.
kevin cline