web-dev-qa-db-fra.com

Vous ne pouvez pas filtrer-> forEach-> collect dans un flux?

Je veux réaliser quelque chose comme ça:

items.stream()
    .filter(s-> s.contains("B"))
    .forEach(s-> s.setState("ok"))
.collect(Collectors.toList());

filtrer, puis modifiez une propriété du résultat filtré, puis collectez le résultat dans une liste. Cependant, le débogueur dit:

Impossible d'appeler collect(Collectors.toList()) sur le type primitif void.

Ai-je besoin de 2 flux pour cela?

13
nimo23

La forEach est conçue pour être une opération de terminal et oui - vous ne pouvez rien faire après l'avoir appelée.

La méthode idiomatique serait d’appliquer d’abord une transformation, puis collect() à la structure de données souhaitée. 

La transformation peut être effectuée à l'aide de map, conçu pour les opérations non mutantes.

Si vous effectuez une opération non-mutante:

 items.stream()
   .filter(s -> s.contains("B"))
   .map(s -> s.withState("ok"))
   .collect(Collectors.toList());

withState est une méthode qui renvoie une copie de l'objet d'origine, y compris la modification fournie.


Si vous effectuez un effet secondaire:

items.stream()
  .filter(s -> s.contains("B"))
  .collect(Collectors.toList());

items.forEach(s -> s.setState("ok"))
13
Grzegorz Piwowarek

Remplacez forEach par map.

 items.stream()
      .filter(s-> s.contains("B"))
      .map(s-> {s.setState("ok");return s;})
      .collect(Collectors.toList());

forEach et collect sont les deux opérations de terminal - Les flux ne doivent en avoir qu'un. Tout ce qui retourne un Stream<T> est un intermediate operation, tout autre élément est un terminal operation

11
Eugene

Résistez à l'envie d'utiliser des effets secondaires de l'intérieur du flux sans une très bonne raison. Créez la nouvelle liste, puis appliquez les modifications:

List<MyObj> toProcess = items.stream()
    .filter(s -> s.contains("B"))
    .collect(toList());

toProcess.forEach(s -> s.setState("ok"));
4
Misha

forEach est une opération de terminal, cela signifie qu’elle produit un résultat hors flux. forEach ne produit rien et collect renvoie une collection. Ce dont vous avez besoin est une opération de flux qui modifie des éléments pour vos besoins. Cette opération est map, ce qui vous permet de spécifier une fonction à appliquer à chaque élément du flux d'entrée et de générer un flux transformé d'éléments. Donc vous avez besoin de quelque chose comme:

items.stream()
     .filter (s -> s.contains("B"))
     .map    (s -> { s.setState("ok"); return s; }) // need to return a value here
     .collect(Collectors.toList());

Une alternative consiste à utiliser peek dans le but d'appliquer une fonction à chaque élément traversant (mais son objectif principal est le débogage):

items.stream()
     .filter (s -> s.contains("B"))
     .peek   (s -> s.setState("ok")) // no need to return a value here
     .collect(Collectors.toList());
4

Vous ne pouvez pas exécuter deux opérations de terminal sur le même flux.

Vous pouvez définir l'état de l'objet dans une opération intermédiaire, telle que map:

List<YourClass> list = 
    items.stream()
         .filter(s-> s.contains("B"))
         .map(s-> {
                      s.setState("ok"); 
                      return s;
                  })
         .collect(Collectors.toList());
2
Eran
 items.stream()
      .filter(s-> s.contains("B"))
      .peek(s-> s.setState("ok"))
      .collect(Collectors.toList());

Stream peek (action du consommateur) Retourne un flux constitué de des éléments de ce flux, effectuant en outre le fourni action sur chaque élément à mesure que les éléments sont consommés à partir du résultat courant. C'est une opération intermédiaire.

Pour les pipelines de flux parallèles, l'action peut être appelée à n'importe quel moment le temps et dans n'importe quel fil l'élément est rendu disponible par le fonctionnement en amont. Si l'action modifie l'état partagé, il s'agit de responsable de la synchronisation requise.

Note de l'API: Cette méthode existe principalement pour prendre en charge le débogage, où vous veulent voir les éléments lorsqu'ils passent un certain point dans un pipeline:

 Stream.of("one", "two", "three", "four")
     .filter(e -> e.length() > 3)
     .peek(e -> System.out.println("Filtered value: " + e))
     .map(String::toUpperCase)
     .peek(e -> System.out.println("Mapped value: " + e))
     .collect(Collectors.toList());   Parameters: action - a non-interfering action to perform on the elements as they are consumed

depuis le flux Retour: le nouveau flux

https://docs.Oracle.com/javase/8/docs/api/Java/util/stream/Stream.html#peek-Java.util.function.Consumer-

0
宏杰李