web-dev-qa-db-fra.com

Pourquoi findFirst () lève-t-il une exception NullPointerException si le premier élément trouvé est null?

Pourquoi cela jette-t-il un Java.lang.NullPointerException?

List<String> strings = new ArrayList<>();
        strings.add(null);
        strings.add("test");

        String firstString = strings.stream()
                .findFirst()      // Exception thrown here
                .orElse("StringWhenListIsEmpty");
                //.orElse(null);  // Changing the `orElse()` to avoid ambiguity

Le premier élément de strings est null, ce qui est une valeur parfaitement acceptable. De plus, findFirst() renvoie un facultatif , ce qui est encore plus logique pour que findFirst() puisse gérer nulls.

EDIT: a mis à jour la orElse() pour qu'elle soit moins ambiguë.

73
neverendingqs

La raison en est l'utilisation de Optional<T> Dans le retour. Facultatif n'est pas autorisé à contenir null. Essentiellement, cela n'offre aucun moyen de distinguer les situations "ce n'est pas là" et "c'est là, mais il est défini sur null".

C'est pourquoi la documentation interdit explicitement la situation lorsque null est sélectionné dans findFirst():

Lance:

NullPointerException - si l'élément sélectionné est null

60
dasblinkenlight

Comme déjà discuté , les concepteurs d'API ne supposent pas que le développeur souhaite traiter les valeurs null et les valeurs absentes de la même manière.

Si vous voulez toujours le faire, vous pouvez le faire explicitement en appliquant la séquence

.map(Optional::ofNullable).findFirst().flatMap(Function.identity())

au ruisseau. Le résultat sera un optionnel vide dans les deux cas, s'il n'y a pas de premier élément ou si le premier élément est null. Donc, dans votre cas, vous pouvez utiliser

String firstString = strings.stream()
    .map(Optional::ofNullable).findFirst().flatMap(Function.identity())
    .orElse(null);

pour obtenir une valeur null si le premier élément est absent ou null.

Si vous voulez faire la distinction entre ces cas, vous pouvez simplement omettre l’étape flatMap:

Optional<String> firstString = strings.stream()
    .map(Optional::ofNullable).findFirst().orElse(null);
System.out.println(firstString==null? "no such element":
                   firstString.orElse("first element is null"));

Ce n'est pas très différent de votre question mise à jour. Vous devez juste remplacer "no such element" avec "StringWhenListIsEmpty" et "first element is null" avec null. Mais si vous n’aimez pas les conditionnels, vous pouvez le réaliser aussi:

String firstString = strings.stream().skip(0)
    .map(Optional::ofNullable).findFirst()
    .orElseGet(()->Optional.of("StringWhenListIsEmpty"))
    .orElse(null);

Maintenant, firstString sera null si un élément existe mais est null et il sera "StringWhenListIsEmpty" quand aucun élément n'existe.

40
Holger

Le code suivant remplace findFirst() par limit(1) et remplace orElse() par reduce():

String firstString = strings.
   stream().
   limit(1).
   reduce("StringWhenListIsEmpty", (first, second) -> second);

limit() ne permet qu'à 1 élément d'atteindre reduce. Le BinaryOperator passé à reduce renvoie cet élément 1 ou bien "StringWhenListIsEmpty" Si aucun élément n'atteint le reduce.

L'avantage de cette solution est que Optional n'est pas alloué et que BinaryOperator lambda n'allouera rien.

11
Nathan

Vous pouvez utiliser Java.util.Objects.nonNull pour filtrer la liste avant de trouver

quelque chose comme

list.stream().filter(Objects::nonNull).findFirst();
7
Mattos

Facultatif est supposé être un type "valeur". (lisez les détails en javadoc :) La machine virtuelle Java pourrait même remplacer tous les Optional<Foo> par Foo, supprimant ainsi tous les coûts de boxe et de déballage. Un null Foo signifie un Optional<Foo> Vide.

Il est possible d’autoriser Facultatif avec la valeur null, sans ajouter d’indicateur booléen - il suffit d’ajouter un objet sentinel. (pourrait même utiliser this comme sentinelle; voir Throwable.cause)

La décision selon laquelle Optional ne peut pas envelopper la valeur null n'est pas basée sur le coût d'exécution. C’était un problème extrêmement controversé et vous devez creuser les listes de diffusion. La décision n'est pas convaincante pour tout le monde.

Quoi qu'il en soit, comme Optional ne peut pas envelopper la valeur null, il nous pousse dans un coin dans des cas comme findFirst. Ils ont dû expliquer que les valeurs nulles sont très rares (il a même été considéré que Stream devrait interdire les valeurs nulles), il est donc plus pratique de lever une exception sur les valeurs nulles plutôt que sur les flux vides.

Une solution de contournement consiste à box null, par exemple.

class Box<T>
    static Box<T> of(T value){ .. }

Optional<Box<String>> first = stream.map(Box::of).findFirst();

(Ils disent que la solution à chaque problème OOP est d'introduire un autre type :)

1
ZhongYu