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 null
s.
EDIT: a mis à jour la orElse()
pour qu'elle soit moins ambiguë.
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é estnull
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.
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.
Vous pouvez utiliser Java.util.Objects.nonNull
pour filtrer la liste avant de trouver
quelque chose comme
list.stream().filter(Objects::nonNull).findFirst();
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 :)