web-dev-qa-db-fra.com

Comment devrions-nous gérer le flux jdk8 pour les valeurs NULL

Bonjour chers collègues Java,

Je sais que le sujet est peut-être un peu in advance _ car le JDK8 n’est pas encore publié (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 appelée Stream.

Voici l'exemple donné dans le article de Java Magazine (c'est un algorithme de population de loutre ..):

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 la suivante: que se passera-t-il si, au milieu de l'itération interne Set, l'une des loutres est nulle?

Je m'attendrais à une exception NullPointerException, mais peut-être suis-je toujours bloqué dans le paradigme de développement précédent (non fonctionnel), quelqu'un peut-il m'éclairer sur la manière de gérer cela?

Si cela jette vraiment une exception NullPointerException, je trouve la fonctionnalité assez dangereuse et ne devra être utilisée que comme ci-dessous:

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

Quelle est la meilleure option ou toute autre option?

Merci!

77
clement

L’idé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. Voir la discussion of nulls sur la liste de diffusion du groupe d’experts Lambda Libraries, en particulier ce message . Un consensus autour de l'option n ° 3 a ensuite émergé (avec une objection notable de Doug Lea). Alors oui, l'inquiétude de l'OP concernant l'explosion de pipelines avec NPE est valable.

Ce n’est pas pour rien que Tony Hoare a appelé les nulls les "Erreur de milliard de dollars." Faire face aux nuls est une vraie galère. Même avec des collections classiques (sans tenir compte des lambdas ni des ruisseaux), les valeurs nulles sont problématiques. Comme fge mentionné dans un commentaire, certaines collections autorisent les valeurs NULL et d'autres non. Avec les collections qui autorisent les valeurs NULL, cela introduit des ambiguïtés dans l'API. Par exemple, avec Map.get () , un retour nul indique que la clé est présente et que sa valeur est null ou 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 consiste à indiquer l’absence de valeur. L’approche proposée pour Java SE 8 est d’introduire un nouveau Java.util.Optional type, qui encapsule la présence/l’absence d’une valeur, ainsi que les comportements de fourniture d’une valeur par défaut, de levée d’une exception, d’appel d’une fonction, etc. si la valeur est absente. Optional n'est utilisé que par les nouvelles API, cependant, tout le reste du système doit encore supporter la possibilité de valeurs NULL.

Mon conseil est d'éviter autant que possible les références nulles réelles. Il est difficile de voir à partir de l'exemple comment il pourrait y avoir une Otter "nulle". Mais si cela était nécessaire, les suggestions du PO en matière de filtrage des valeurs nulles ou de leur correspondance avec un objet sentinelle (le Null Object Pattern ) sont des approches raffinées.

39
Stuart Marks

Bien que les réponses soient correctes à 100%, une petite suggestion pour améliorer null la gestion des requêtes 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 gérer correctement le cas où listOfStuff sera nul et renverra une liste vide au lieu d'échouer avec une exception NullPointerException.

68
Johnny

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

Je rencontrais ce problème lorsque j'essayais d'exécuter un reduce sur un flux contenant des valeurs nulles (en réalité, il s'agissait de LongStream.average(), qui est un type de réduction). Puisque average () renvoie OptionalDouble, j'ai supposé que le flux pourrait contenir des valeurs NULL, mais une exception NullPointerException a été émise. Cela est dû à explication de Stuart sur null v.

Donc, comme l’OP le suggère, j’ai ajouté un filtre comme celui-ci:

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

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

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

À partir de la discussion sur la liste de diffusion Stuart liée: Brian Goetz sur les Nuls dans Streams

64
bparry

Si vous souhaitez simplement filtrer les valeurs null 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 va imprimer:

Foo
Bar
18
Andy Thomas

Un exemple comment éviter null, par exemple utiliser le filtre avant de grouperPar

Filtrez les instances nulles avant de grouperPar.

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