web-dev-qa-db-fra.com

Comment faire pour que Java 8 Stream mappe en permanence avec une vérification nulle

J'ai ce morceau de code

Coverage mainCoverage = illus.getLifes().stream()
    .filter(Life::isIsmain)
    .findFirst()
    .orElseThrow(() -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002")))
    .getCoverages()  
    .stream() // <==may cause null here if list coverage is null
    .filter(Coverage::isMainplan)
    .findFirst()
    .orElseThrow(() -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002")));

qui est totalement bon travail mais je pense que c'est un peu désordonné et ne couvre pas tout le null pointer exception possible (voir le commentaire).

J'essaie de refactoriser ce code en

Coverage mainCoverage1 = illus.getLifes().stream()
    .filter(Life::isIsmain)
    .map(Life::getCoverages)
    .filter(Coverage::isMainplan) //<== cannot filter from list coverage to one main coverage
    ...

Apparemment, après avoir associé la vie à la couverture, il ne s'agit plus d'une liste de couverture. La question est donc de savoir comment je peux refactoriser la première section en null safe et peut-être la raccourcir?

8
Dang Nguyen

Life::getCoverages retourne une collection; par conséquent, le filtre Coverage::isMainplan ne fonctionnera pas. Vous devez plutôt flatMap les séquences renvoyées après .map(Life::getCoverages), puis appliquer l'opération filter à la Coverage:

Coverage mainCoverage = 
          illus.getLifes()
               .stream()
               .filter(Life::isIsmain)               
               .map(Life::getCoverages)
               //.filter(Objects::nonNull) uncomment if there can be null lists
               .flatMap(Collection::stream) // <--- collapse the nested sequences
               //.filter(Objects::nonNull) // uncomment if there can be null Coverage
               .filter(Coverage::isMainplan)
               .findFirst().orElse(...);

J'ai ajouté quelques éléments à votre code:

  1. J'ai ajouté .filter(Objects::nonNull) after .map(Life::getCoverages) que vous pouvez supprimer, étant donné que les éléments renvoyés sont potentiellement nuls.
  2. J'ai ajouté .flatMap(Collection::stream) qui renvoie un flux contenant les résultats du remplacement de chaque élément de ce flux par le contenu d'un flux mappé produit en appliquant la fonction de mappage fournie à chaque élément. 
  3. J'ai ajouté une autre .filter(Objects::nonNull) que vous pouvez supprimer, étant donné que les éléments retournés après flatMap pourraient potentiellement être nuls.
  4. Nous sommes alors à un stade où nous pouvons appliquer .filter(Coverage::isMainplan) et, enfin, récupérer le premier objet répondant aux critères via findFirst et si aucun ne fournit alors une valeur par défaut via orElse.

Je vous suggère de consulter les blogs suivants pour vous familiariser avec la méthode flatMap:

4
Aomine

Ajouter une condition dans filter si list n'est pas null et i.isIsmain, alors seulement filtrer, vous pouvez utiliser public static boolean isNull(Object obj) ou public static boolean nonNull(Object obj)

Coverage mainCoverage = illus.getLifes().stream()
.filter(i->i.isIsmain && Objects.nonNull(i.getCoverages()))
.findFirst()
.orElseThrow(() -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002")))
.getCoverages()  
.stream() // <==may cause null here if list coverage is null
.filter(Coverage::isMainplan)
.findFirst()
.orElseThrow(() -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002")));
3
Deadpool

Dans la première partie de votre code, vous pouvez insérer une filter(e -> e != null) pour savoir si la variable List est nulle et ne lancera pas de NPE:

Coverage mainCoverage = illus.getLifes().stream()
         .filter(Life::isIsmain)
         .findFirst()
         .orElseThrow(() -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002")))
         .getCoverages()  
         .filter(e -> e != null) //<=== Filter out all null values
         .stream()
         .filter(Coverage::isMainplan)
         .findFirst()
         .orElseThrow(() -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002"))

Le problème avec votre deuxième extrait de code est que je suppose que Life::getCoverages renvoie un objet Collection, et non un objet Coverage, vous ne pouvez donc pas appeler Coverage::isMainplan dessus.

2
GBlodgett

Vous pouvez essayer d'encapsuler le Collection<Coverage> résultant dans un Optional<Collection<Coverage>> afin de pouvoir effectuer un mappage de manière null safe.

final Supplier<ServiceInvalidAgurmentGeneraliException> customExceptionThrower = () -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002"));

final Collection<Coverage> firstMainLifeCoverages = illus.getLifes().stream()
    .filter(Life::isIsmain)
    .findFirst()
    .orElseThrow(customExceptionThrower)
    .getCoverages();

Optional.ofNullable(firstMainLifeCoverages)
    .map(Collection::stream)
    .orElseThrow(customExceptionThrower)
    .filter(Coverage::isMainplan)
    .findFirst()
    .orElseThrow(customExceptionThrower);
0
HPH