Comment gérer le flux jdk8 pour les valeurs nulles

88

Bonjour chers développeurs Java,

Je sais que le sujet peut être un peu in advancecar le JDK8 n'est pas encore sorti (et pas pour l'instant en tout cas ..) mais je lisais quelques articles sur les expressions Lambda et en particulier la partie liée à la nouvelle API de collection connue sous le nom de Stream.

Voici l'exemple donné dans l' article de Java Magazine (il s'agit d'un algorithme de population de loutres ..):

Set<Otter> otters = getOtters();
System.out.println(otters.stream()
    .filter(o -> !o.isWild())
    .map(o -> o.getKeeper())
    .filter(k -> k.isFemale())
    .into(new ArrayList<>())
    .size());

Ma question est que se passe-t-il si au milieu de l'itération interne Set, l'une des loutres est nulle?

Je m'attendrais à ce qu'une exception NullPointerException soit lancée, mais je suis peut-être toujours coincé dans le paradigme de développement précédent (non fonctionnel), quelqu'un peut-il m'éclairer sur la façon dont cela devrait être géré?

Si cela lève vraiment une NullPointerException, je trouve la fonctionnalité assez dangereuse et devra être utilisée uniquement comme ci-dessous:

  • Développeur pour s'assurer qu'il n'y a pas de valeur nulle (peut-être en utilisant un précédent .filter (o -> o! = Null))
  • Développeur pour s'assurer que l'application ne génère jamais une loutre nulle ou un objet NullOtter spécial à traiter.

Quelle est la meilleure option ou toute autre option?

Merci!

clément
la source
3
Je dirais que c'est au programmeur de faire ce qu'il faut ici; la JVM et le compilateur ne peuvent pas faire grand-chose. Notez que certaines implémentations de collection n'autorisent cependant pas les valeurs nulles.
fge
18
Vous pouvez utiliser filter(Objects::nonNull)avec Objectsdejava.utils
Benj

Réponses:

45

La pensée actuelle semble être de «tolérer» les valeurs nulles, c'est-à-dire de les autoriser en général, bien que certaines opérations soient moins tolérantes et puissent finir par lancer des NPE. Consultez la discussion sur les valeurs nulles sur la liste de diffusion du groupe d'experts des bibliothèques Lambda, en particulier ce message . Un consensus autour de l'option 3 est apparu par la suite (avec une objection notable de Doug Lea). Alors oui, la préoccupation du PO concernant l'explosion de pipelines avec du NPE est valide.

Ce n'est pas pour rien que Tony Hoare a qualifié les nulls d ' «erreur d'un milliard de dollars». Traiter les nulls est une vraie douleur. Même avec des collections classiques (sans tenir compte des lambdas ou des flux), les valeurs nulles sont problématiques. Comme fge l'a mentionné dans un commentaire, certaines collections autorisent les valeurs nulles et d'autres non. Avec des collections qui autorisent les valeurs nulles, cela introduit des ambiguïtés dans l'API. Par exemple, avec Map.get () , un retour nul indique soit que la clé est présente et que sa valeur est nulle, soit que la clé est absente. Il faut faire un travail supplémentaire pour lever l'ambiguïté de ces cas.

L'utilisation habituelle de null est de désigner l'absence de valeur. L'approche pour traiter ce problème proposée pour Java SE 8 est d'introduire un nouveau java.util.Optionaltype, qui encapsule la présence / l'absence d'une valeur, ainsi que les comportements consistant à fournir une valeur par défaut, à lancer une exception, ou à appeler une fonction, etc. si la valeur est absente. Optionaln'est utilisé que par les nouvelles API, cependant, tout le reste du système doit encore supporter la possibilité de valeurs nulles.

Mon conseil est d'éviter au maximum les références nulles réelles. Il est difficile de voir à partir de l'exemple donné comment il pourrait y avoir un Otter "nul". Mais si cela était nécessaire, les suggestions de l'OP de filtrer les valeurs nulles, ou de les mapper à un objet sentinelle (le modèle d'objet nul ) sont de bonnes approches.

Marques Stuart
la source
3
Pourquoi éviter les valeurs nulles? Ils sont largement utilisés dans les bases de données.
Raffi Khatchadourian
5
@RaffiKhatchadourian Oui, les valeurs nulles sont utilisées dans les bases de données, mais sont tout aussi problématiques. Voyez ceci et cela et lisez toutes les réponses et commentaires. Considérez également l'impact de SQL null sur les expressions booléennes: en.wikipedia.org/wiki/… ... c'est une riche source d'erreurs de requête.
Stuart marque
1
@RaffiKhatchadourian Parce que c'est nullnul, c'est pourquoi. Acc. à une enquête que j'ai vue (je ne la trouve pas), NPE est l'exception numéro 1 en Java. SQL n'est pas un langage de programmation de haut niveau, certainement pas fonctionnel, qui méprise null, donc il s'en fiche.
Abhijit Sarkar le
91

Bien que les réponses soient correctes à 100%, une petite suggestion pour améliorer la nullgestion des cas de la liste elle-même avec facultatif :

 List<String> listOfStuffFiltered = Optional.ofNullable(listOfStuff)
                .orElseGet(Collections::emptyList)
                .stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

La partie Optional.ofNullable(listOfStuff).orElseGet(Collections::emptyList)vous permettra de bien gérer le cas où listOfStuffest null et de renvoyer un emptyList au lieu d'échouer avec NullPointerException.

Johnny
la source
2
J'aime ça, évite de vérifier explicitement null.
Chris
1
cela semble mieux clair .. agréable et exactement ce dont j'avais besoin
Abdullah Al Noman
Il vérifie explicitement null, mais d'une manière différente. S'il est autorisé à être nul, cela devrait être facultatif, c'est l'idée, non? Bannissez toutes les valeurs nulles avec des options. De plus, c'est une mauvaise pratique de retourner null au lieu d'une liste vide, donc cela montre deux odeurs de code si je vois ceci: aucun Optionnel n'est utilisé, et aucun flux vide n'est retourné. Et ce dernier est disponible depuis plus de 20 ans, donc c'est mature ...
Koos Gadellaa
que faire si je veux retourner une liste nulle au lieu de vide?
Ashburn RK
@AshburnRK c'est une mauvaise pratique. Vous devez renvoyer une liste vide.
Johnny
69

La réponse de Stuart fournit une excellente explication, mais j'aimerais donner un autre exemple.

J'ai rencontré ce problème en essayant d'effectuer un reducesur un flux contenant des valeurs nulles (en fait, c'était LongStream.average()un type de réduction). Depuis le retour de average () OptionalDouble, j'ai supposé que le Stream pouvait contenir des valeurs nulles, mais à la place, une NullPointerException a été lancée. Cela est dû à l'explication de Stuart de null contre vide.

Donc, comme le suggère l'OP, j'ai ajouté un filtre comme ceci:

list.stream()
    .filter(o -> o != null)
    .reduce(..);

Ou comme tangens l'a souligné ci-dessous, utilisez le prédicat fourni par l'API Java:

list.stream()
    .filter(Objects::nonNull)
    .reduce(..);

De la discussion de la liste de diffusion Stuart a lié: Brian Goetz sur les nulls dans Streams

bparry
la source
19

Si vous souhaitez simplement filtrer les valeurs nulles d'un flux, vous pouvez simplement utiliser une référence de méthode à java.util.Objects.nonNull (Object) . De sa documentation:

Cette méthode existe pour être utilisée comme prédicat ,filter(Objects::nonNull)

Par exemple:

List<String> list = Arrays.asList( null, "Foo", null, "Bar", null, null);

list.stream()
    .filter( Objects::nonNull )  // <-- Filter out null values
    .forEach( System.out::println );

Cela imprimera:

Foo
Bar
Andy Thomas
la source
7

Un exemple comment éviter null, par exemple, utilisez le filtre avant de grouper

Filtrez les instances nulles avant groupingBy.

Voici un exemple

MyObjectlist.stream()
            .filter(p -> p.getSomeInstance() != null)
            .collect(Collectors.groupingBy(MyObject::getSomeInstance));
Ilan M
la source