Как получить первый элемент при продолжении потоковой передачи? - PullRequest
28 голосов
/ 06 марта 2019

У меня есть поток общих предметов. Я хотел бы напечатать имя класса первого элемента + toString() всех элементов.

Если бы у меня был Iterable, он бы выглядел так:

Iterable<E> itemIter = ...;
boolean first = true;
for (E e : itemIter) {
    if (first) {
        first = false;
        System.out.println(e.getClass().getSimpleName());
    }
    System.out.println(e);
}

Можно ли сделать это в потоке (Stream<T>) с помощью потокового API?

* Обратите внимание, что речь идет о потоках, а не об итераторах. У меня есть поток, а не итератор.

Ответы [ 7 ]

16 голосов
/ 06 марта 2019

Существует StreamEx библиотека, которая расширяет стандартный Java Stream API.Использование StreamEx.of(Iterator) и peekFirst:

StreamEx.of(itemIter.iterator())
        .peekFirst(e -> System.out.println(e.getClass().getSimpleName()))
        .forEach(System.out::println);
11 голосов
/ 06 марта 2019

Собственное решение: Stream в Java не может использоваться повторно.Это означает, что поток потребления может быть выполнен только один раз.Если вы получаете первый элемент из потока, вы можете повторить его еще раз.

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

Stream<E> stream = StreamSupport.stream(Spliterators.spliteratorUnknownSize(sourceIterator, Spliterator.ORDERED), false);
E firstElement = itemIter.next();
stream.foreach(...);

Редактировать

На самом деле нет никакого способа "скопировать" поток, вам нужно сохранить итератор / коллекцию.Подробнее об этом здесь .Когда дело доходит до памяти, когда поток исчерпан, он может быть собран сборщиком мусора, так как от него нет никакой пользы.Сам поток не занимает больше места, чем итератор, из которого он исходит.Имейте в виду, что потоки могут быть потенциально бесконечными.Элементы, которыми в данный момент манипулируют, хранятся в памяти.

9 голосов
/ 06 марта 2019

Если ваша отправная точка - Stream и вы хотите сохранить все ее свойства и лень, подойдет следующее решение:

public static <E> Stream<E> forFirst(Stream<E> stream, Consumer<? super E> c) {
    boolean parallel = stream.isParallel();
    Spliterator<E> sp = stream.spliterator();
    return StreamSupport.stream(() -> {
        if(sp.getExactSizeIfKnown() == 0) return sp;
        Stream.Builder<E> b = Stream.builder();
        if(!sp.tryAdvance(b.andThen(c))) return sp;
        return Stream.concat(b.build(), StreamSupport.stream(sp, parallel)).spliterator();
    }, sp.characteristics(), parallel);
}

например. когда вы используете его с

List<String> list = new ArrayList<>(List.of("foo", "bar", "baz"));
Stream<String> stream = forFirst(
        list.stream().filter(s -> s.startsWith("b")),
        s -> System.out.println(s+" ("+s.getClass().getSimpleName()+')')
    ).map(String::toUpperCase);
list.add(1, "blah");
System.out.println(stream.collect(Collectors.joining(" | ")));

распечатает

blah (String)
BLAH | BAR | BAZ

демонстрирует, что обработка не начнется до начала операции терминала (collect), следовательно, отражает предыдущее обновление источника List.

6 голосов
/ 06 марта 2019

Вы можете злоупотреблять сокращением:

Stream<E> stream = ...;
System.out.println(stream
                   .reduce("",(out,e) -> 
                   out + (out.isEmpty() ? e.getClass().getSimpleName()+"\n" : "")
                   + e));
0 голосов
/ 06 марта 2019

Вы также можете использовать логическую атомную ссылку:

AtomicReference<Boolean> first = new AtomicReference<Boolean>(Boolean.TRUE);
stream.forEach(e -> 
         System.out.println("First == " + first.getAndUpdate(b -> false)));
0 голосов
/ 06 марта 2019

Один из обходных путей - сделать это следующим образом -

import java.util.*; 
import java.util.stream.Collectors;
public class MyClass {
   static int i = 0;
   static int getCounter(){
       return i;
   }
   static void incrementCounter(){
       i++;
   }
    public static void main(String args[]) {
        List<String> list = Arrays.asList("A", "B", "C", "D", "E", "F", "G"); 
        List<String> answer = list.stream().filter(str -> {if(getCounter()==0) {System.out.println("First Element : " + str);} incrementCounter(); return true;}). 
        collect(Collectors.toList()); 
        System.out.println(answer); 
    }
}

Вывод:

First Element : A
[A, B, C, D, E, F, G]
0 голосов
/ 06 марта 2019

Вы можете использовать peek для этого:

AtomicBoolean first = new AtomicBoolean(true);
StreamSupport.stream(itemIter.spliterator(), false)
    .peek(e -> {
        if(first.get()) {
            System.out.println(e.getClass().getSimpleName());
            first.set(false);
        }
    })
    ...
...