Как получить первый элемент при продолжении потоковой передачи?

У меня есть поток общих предметов. Я хотел бы напечатать имя класса первого элемента + 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?

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

Ответ 1

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

StreamEx.of(itemIter.iterator())
        .peekFirst(e -> System.out.println(e.getClass().getSimpleName()))
        .forEach(System.out::println);

Ответ 2

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

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

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

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

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

Ответ 3

Если вашей отправной точкой является 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 источников.

Ответ 4

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

Stream<E> stream = ...;
System.out.println(stream
                   .reduce("",(out,e) -> 
                   out + (out.isEmpty() ? e.getClass().getSimpleName()+"\n" : "")
                   + e));

Ответ 5

Вы можете использовать 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);
        }
    })
    ...

Ответ 6

Один из способов это сделать так -

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]

Ответ 7

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

AtomicReference<Boolean> first = new AtomicReference<Boolean>(Boolean.TRUE);
stream.forEach(e -> 
         System.out.println("First == " + first.getAndUpdate(b -> false)));