Filtrer les valeurs uniquement si elles ne sont pas nulles à l'aide de lambda dans Java8

161

J'ai une liste d'objets à dire car. Je veux filtrer cette liste en fonction d'un paramètre en utilisant Java 8. Mais si le paramètre est null, il jette NullPointerException. Comment filtrer les valeurs nulles?

Le code actuel est le suivant

requiredCars = cars.stream().filter(c -> c.getName().startsWith("M"));

Cela jette NullPointerExceptionsi getName()revient null.

vaibhavvc1092
la source
Voulez-vous "filtrer les valeurs uniquement si elles ne sont pas nulles" ou "filtrer les valeurs nulles"? Cela me semble contradictoire.
Holger
3
Puis-je suggérer que vous acceptiez la réponse de Tunaki car elle semble être la seule qui réponde réellement à votre question.
Mark Booth

Réponses:

323

Dans cet exemple particulier, je pense que @Tagir est correct à 100%, mettez-le dans un filtre et effectuez les deux vérifications. Je n'utiliserais pas Optional.ofNullablele truc optionnel, c'est vraiment pour les types de retour de ne pas faire de logique ... mais vraiment ni ici ni là-bas.

Je voulais souligner qu'il java.util.Objectsexiste une méthode intéressante pour cela dans un cas large, vous pouvez donc le faire:

cars.stream()
    .filter(Objects::nonNull)

Ce qui effacera vos objets nuls. Pour ceux qui ne sont pas familiers, c'est le raccourci pour ce qui suit:

cars.stream()
    .filter(car -> Objects.nonNull(car))

Pour répondre partiellement à la question posée, renvoyez la liste des noms de voitures commençant par "M":

cars.stream()
    .filter(car -> Objects.nonNull(car))
    .map(car -> car.getName())
    .filter(carName -> Objects.nonNull(carName))
    .filter(carName -> carName.startsWith("M"))
    .collect(Collectors.toList());

Une fois que vous vous êtes habitué aux lambdas abrégés, vous pouvez également le faire:

cars.stream()
    .filter(Objects::nonNull)
    .map(Car::getName)        // Assume the class name for car is Car
    .filter(Objects::nonNull)
    .filter(carName -> carName.startsWith("M"))
    .collect(Collectors.toList());

Malheureusement, une fois que vous .map(Car::getName)êtes, vous ne retournerez que la liste des noms, pas les voitures. Donc moins beau mais répond pleinement à la question:

cars.stream()
    .filter(car -> Objects.nonNull(car))
    .filter(car -> Objects.nonNull(car.getName()))
    .filter(car -> car.getName().startsWith("M"))
    .collect(Collectors.toList());
xbakesx
la source
1
notez que la voiture nulle n'est pas le problème. Dans ce cas, c'est la propriété de nom qui pose des problèmes. Donc, Objects::nonNullne peut pas être utilisé ici, et dans le dernier conseil, cela devrait être, cars.stream() .filter(car -> Objects.nonNull(car.getName()))je crois
kiedysktos
1
BTW, je pense que cars.stream() .filter(car -> Objects.nonNull(car.getName()) && car.getName().startsWith("M"))serait le résumé de vos conseils dans ce contexte de question
kiedysktos
3
@kiedysktos C'est un bon point que l'appel .startWithpourrait également provoquer un pointeur nul. Le point que j'essayais de souligner est que Java fournit une méthode spécifiquement pour filtrer les objets nuls de vos flux.
xbakesx
@Mark Booth oui, c'est évidemment Objects.nonNulléquivalent à != null, votre option est plus courte
kiedysktos
1
Ne créez-vous pas une liste de noms de voitures ( String) à la place de voitures ( Car)?
user1803551
59

Il vous suffit de filtrer les voitures qui ont un nullnom:

requiredCars = cars.stream()
                   .filter(c -> c.getName() != null)
                   .filter(c -> c.getName().startsWith("M"));
Tunaki
la source
3
C'est vraiment dommage que cette réponse ne soit pas plus votée car elle semble être la seule réponse qui répond réellement à la question.
Mark Booth
@MarkBooth La question "Comment filtrer les valeurs nulles?" semble bien répondre par xbakesx.
vegemite4me
@MarkBooth En regardant les dates, vous avez raison. Mon erreur.
vegemite4me
En termes de performances, est-il bon de filtrer le flux deux fois ou mieux utiliser le prédicat pour le filtrage? Veux juste savoir.
Vaibhav_Sharma
51

Les réponses proposées sont excellentes. Voudrais simplement suggérer une amélioration de traiter le cas de la liste null en utilisant Optional.ofNullable, nouvelle fonctionnalité en Java 8 :

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

Donc, la réponse complète sera:

 List<String> carsFiltered = Optional.ofNullable(cars)
                .orElseGet(Collections::emptyList)
                .stream()
                .filter(Objects::nonNull) //filtering car object that are null
                .map(Car::getName) //now it's a stream of Strings
                .filter(Objects::nonNull) //filtering null in Strings
                .filter(name -> name.startsWith("M"))
                .collect(Collectors.toList()); //back to List of Strings
Johnny
la source
5
Mauvaise utilisation d'Optionnel. null ne doit jamais être utilisé comme synonyme d'une Collection vide en premier lieu.
VGR
5
@VGR Bien sûr, mais ce n'est pas ce qui se passe dans la pratique. Parfois (la plupart du temps), vous devez travailler avec du code sur lequel beaucoup de gens ont travaillé. Parfois, vous recevez vos données d'interfaces externes. Pour tous ces cas, Optionnel est une excellente utilisation.
Johnny
2
notez que la voiture nulle n'est pas le problème. Dans ce cas, c'est la propriété de nom qui pose des problèmes. Cela Objects::nonNullne résout donc pas le problème car une voiture non nulle peut avoir le nom == null
kiedysktos
1
Bien sûr @kiedysktos, mais ce n'est pas ce que je voulais montrer dans la réponse. Mais, j'accepte ce que vous dites et je modifie la réponse :)
Johnny
24

Vous pouvez le faire en une seule étape de filtrage:

requiredCars = cars.stream().filter(c -> c.getName() != null && c.getName().startsWith("M"));

Si vous ne souhaitez pas appeler getName()plusieurs fois (par exemple, c'est un appel coûteux), vous pouvez le faire:

requiredCars = cars.stream().filter(c -> {
    String name = c.getName();
    return name != null && name.startsWith("M");
});

Ou de manière plus sophistiquée:

requiredCars = cars.stream().filter(c -> 
    Optional.ofNullable(c.getName()).filter(name -> name.startsWith("M")).isPresent());
Tagir Valeev
la source
L'expansion en ligne dans le deuxième exemple était précieuse pour mon cas d'utilisation
Paul
3

Tirer parti de la puissance de java.util.Optional#map():

List<Car> requiredCars = cars.stream()
  .filter (car -> 
    Optional.ofNullable(car)
      .map(Car::getName)
      .map(name -> name.startsWith("M"))
      .orElse(false) // what to do if either car or getName() yields null? false will filter out the element
    )
  .collect(Collectors.toList())
;
rslemos
la source
1

vous pouvez utiliser ceci

List<Car> requiredCars = cars.stream()
    .filter (t->  t!= null && StringUtils.startsWith(t.getName(),"M"))
    .collect(Collectors.toList());
riverfan
la source