2017-07-07 3 views
4

Compte tenu de ce code:java 8 findFirst vs carte sur option

class Foo { 
    Integer attr; 
    public Integer getAttr() {return attr;} 
} 

List<Foo> list = new ArrayList<>(); 
list.add(new Foo()); 

list.stream().map(Foo::getAttr).findAny().orElse(null); //A 
list.stream().findAny().map(Foo::getAttr).orElse(null); //B 

ligne A lance

java.lang.NullPointerException: null

tandis que la ligne B renvoie null.

Quelle est la cause de ce problème? Les deux findAny() et map() renvoient Optional<T>.

+1

Voir aussi [Pourquoi findFirst() une NullPointerException si le premier élément qu'il trouve est nul?] (Https://stackoverflow.com/q/32466799/2711488) – Holger

Répondre

5
list.stream().map(Foo::getAttr).findAny().orElse(null); 

Java doc for streams dit que Stream « renvoie un flux constitué par les résultats de l'application de la fonction donnée aux éléments de ce courant », et findAny() « pourrait revenir aNullPointerException - si l'élément sélectionné est nul ». Dans votre classe Foo, Integer (pas int) par défaut est défini sur null car il est déclaré mais pas initialisé. voir Primitives see default values et Object initialization in Java

Initialisation est différent pour: A) classe Membres (objets et primitives) B) Variables locales

+0

Vous devez modifier votre réponse en ajoutant source pour cela;) Je les connais mais c'est mieux – AxelH

0
list.stream().map(Foo::getAttr) 

... retourne un flux avec un élément, avec une valeur de null .

Le JavaDoc pour findAny() (et findFirst()) dit:

Retours:

une option décrivant un élément de ce flux, ou un vide en option si le flux est vide

Jets:

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

Alors findAny() fait exactement comme documenté: il est la sélection d'un nul, et en conséquence, jetant NullPointerException.

Cela est logique car Optional est (toujours selon JavaDoc, mais non souligné):

Un objet conteneur qui peut ou non contenir une valeur non nulle

. .. ce qui signifie que vous pouvez garantir que Optional.ifPresent(x -> x.method()) ne lancera jamais NullPointerException en raison de x étant nulle.

Donc, findAny() n'a pas pu renvoyer Optional.of(null). Et Optional.empty() signifie que le flux était vide, pas qu'il ait trouvé une valeur nulle.

De nombreuses parties de l'infrastructure Stream/Optional découragent l'utilisation de valeurs nulles.

Vous pouvez contourner ce problème en mappant les valeurs NULL à Optionals, pour donner un Optional<Optional<Foo>> - ce qui semble un peu compliqué, mais est une représentation précise de votre domaine. Optional.empty() signifie que le flux était vide. Optional.of(Optional.empty()) signifie qu'il a trouvé un élément nul:

list.stream().map(Foo::getAttr).map(Optional::ofNullable).findAny() 
+3

en utilisant un 'flatMap' le rendrait plus propre:' list.stream(). findAny(). flatMap (f -> Optional.ofNullable (f.getAttr())). orElse (null); 'Comme je l'ai écrit ci-dessus ... – Eugene

+1

@Eugene Si vous voulez obtenir des nulls, oui. Dans mon esprit cependant, ce code peut être une frontière entre un domaine où les nulls peuvent exister, et un domaine dans lequel tout est garanti non nul. – slim

3

Eh bien, il est évidemment à cause de l'ordre dans lequel vous effectuez ces opérations et aussi parce que findAny dit explicitement: throws NullPointerException if the element selected is null

Lorsque vous map(Foo::getAttr) vous avez effectivement mappé à null, votre flux contient maintenant un null; ainsi findAny rompt avec une exception (depuis findAny est appliquée sur cette null)

L'autre opération première trouve l'objet Foo, il cartes à Foo::getAttr (il mappage ainsi Optional.empty()), ainsi orElse est appelée.

En outre, ce serait plus logique (pour moi au moins):

list.stream() 
    .findAny() 
    .flatMap(f -> Optional.ofNullable(f.getAttr())) 
    .orElse(null); 

flatMap tracerait à Optional<Integer> (attributs), dans le cas où celui-ci est empty obtenir le résultat orElse.

+3

Ahem, il est impossible d'avoir un mappage 'Optional' à' null'.Un 'Optional 'est soit vide, soit mappé à une valeur non -null'. Le second cas de l'OP, appelant 'map (Foo :: getAttr)' sur un optionnel, fait exactement la même chose que votre 'flatMap (f -> Optional.ofNullable (f.getAttr()))'. Dans les deux cas, le résultat est un vide facultatif et l'argument passé à 'orElse' est renvoyé. (et dire "' orElse' ne s'appelle pas "n'a aucun sens de toute façon.Il n'y a aucun moyen comment l'invocation de' orElse' pourrait être ignorée) – Holger

+0

@Holger ça alors c'était stupide de mon côté, merci – Eugene

2

Tout d'abord, vos deux extrait de code map sont différentes opérations:

//   v--- stream intermediate operation 
list.stream().map(Foo::getAttr).findAny().orElse(null); //A 
//      v---- a Optional utility method 
list.stream().findAny().map(Foo::getAttr).orElse(null); //B 

et le NullPointerException se produit dans Stream#findAny opération, car elle ne peut pas accepter une valeur null. en raison de cela utilise Optional.of plutôt que Optional.ofNullable. et la documentation de Stream#findAny est déjà affirme:

Lancers:

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

donc si vous voulez que votre A extrait de code fonctionne bien vous doit filtrer toutes les valeurs null avant d'appeler le Stream#findAny, par exemple:

//when no elements in stream, `findAny` will be return a empty by Optional.empty() 
//              v 
list.stream().map(Foo::getAttr).filter(Objects::nonNull).findAny().orElse(null);//A 
+1

Bravo! Je pensais que personne ne parlerait de 'filter (Objects :: nonNull)' –