it-swarm.com.ru

Не можете сделать фильтр-> forEach-> собирать в один поток?

Я хочу добиться чего-то вроде этого:

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

фильтр, затем измените свойство из отфильтрованного результата, а затем соберите результат в список. Тем не менее, отладчик говорит:

Невозможно вызвать collect(Collectors.toList()) для типа примитива void.

Мне нужно 2 потока для этого?

13
nimo23

forEach предназначена для терминальной операции и да - вы ничего не можете сделать после вызова.

Идиоматическим способом было бы применить сначала преобразование, а затем collect() все к желаемой структуре данных. 

Преобразование может быть выполнено с использованием map, который предназначен для операций без мутаций.

Если вы выполняете операцию без мутаций:

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

где withState - это метод, который возвращает копию исходного объекта, включая предоставленное изменение.


Если вы выполняете побочный эффект:

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

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

Замените forEach на map.

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

forEach и collect являются обе терминальными операциями - у потоков должен быть только один. Все, что возвращает Stream<T>, является intermediate operation, все остальное - terminal operation

11
Eugene

Не поддавайтесь желанию использовать побочные эффекты изнутри потока без веской причины. Создайте новый список и примените изменения:

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

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

forEach является терминальной операцией, это означает, что она дает не потоковый результат. forEach ничего не производит и collect возвращает коллекцию. Что вам нужно, это потоковая операция, которая изменяет элементы для ваших нужд. Эта операция map, которая позволяет вам указать функцию, которая будет применена к каждому элементу входного потока, и создаст преобразованный поток элементов. Так что вам нужно что-то вроде:

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

Альтернативой является использование peek, целью которого является применение функции к каждому элементу обхода (но его основное назначение - отладка):

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

Вы не можете выполнить две терминальные операции в одном потоке.

Вы можете установить состояние объекта в промежуточной операции, такой как 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 (Consumer action) Возвращает поток, состоящий из элементов этого потока, дополнительно выполняя предоставленную действие на каждый элемент, так как элементы потребляются из результирующего поток. Это промежуточная операция.

Для параллельных потоковых конвейеров действие может быть вызвано как угодно время и в каком-либо потоке элемент сделан доступным операция вверх по течению. Если действие изменяет общее состояние, это ответственность за обеспечение необходимой синхронизации.

Примечание API: Этот метод существует главным образом для поддержки отладки, где вы хотите видеть элементы, проходящие мимо определенной точки в трубопровод:

 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

из потока возвращается: новый поток

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

0
宏杰李