Java 如何在继续流式处理时获取第一个元素?

Java 如何在继续流式处理时获取第一个元素?,java,for-loop,collections,java-8,java-stream,Java,For Loop,Collections,Java 8,Java Stream,我有一个通用项目流。我想打印第一个项的类名+所有项的toString() 如果我有一个Iterable,它会是这样的: Iterable<E> itemIter = ...; boolean first = true; for (E e : itemIter) { if (first) { first = false; System.out.println(e.getClass().getSimpleName()); } Sys

我有一个通用项目流。我想打印第一个项的类名+所有项的
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);
}
Iterable itemIter=。。。;
布尔值优先=真;
对于(E:itemIter){
如果(第一){
第一个=假;
System.out.println(e.getClass().getSimpleName());
}
系统输出打印ln(e);
}
我可以使用流API在流(
)上执行此操作吗


*请注意,这是一个关于流的问题,而不是关于迭代器的问题。我有一个流-不是迭代器。

本机解决方案:
Java中的流
是不可重用的。这意味着,消费流只能执行一次。如果从流中获取第一个元素,则可以对其进行多次迭代

解决方法是创建与第一个流相同的另一个流,或者获取第一个项,然后创建一个流,类似于:

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


实际上没有任何方法“复制”流,您需要保留一个迭代器/集合。更多关于它。一旦流耗尽,当它进入内存时,垃圾收集器可以收集它,因为它没有任何用处。流本身所占用的空间并不比它所源自的迭代器多。请记住,流可能是无限的。当前操作的元素存储在内存中。

您可以:

Stream<E> stream = ...;
System.out.println(stream
                   .reduce("",(out,e) -> 
                   out + (out.isEmpty() ? e.getClass().getSimpleName()+"\n" : "")
                   + e));
Stream=。。。;
System.out.println(流
.reduce(“,(out,e)->
out+(out.isEmpty()?e.getClass().getSimpleName()+“\n:”)
+(e));
有一个扩展标准Java流API的库。使用和:


您可以使用
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);
        }
    })
    ...

一个解决方法是这样做-

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); 
    }
}

您还可以使用布尔原子引用:

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

如果您的起点是一个
,并且希望保留其所有属性和惰性,则以下解决方案可以:

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);
}


证明在开始终端操作(
collect
)之前不会启动处理,因此反映了源
列表的先前更新

,从这个意义上讲,我想即使定义了iterable,所讨论的代码也会更好。请记住,没有比
itemIter.stream()更好的方法了。@nullpointer你说得对,我已经编辑了我的答案以使用StreamSupport传递给
peek
的操作将按处理顺序执行,但不保证是流的遭遇顺序。请注意,这是一个关于流的问题,而不是关于迭代器的问题。我有一个流-不是迭代器。@AndrewTobilko-我没有
Iterable
-只是
stream
。请注意,如果你有一个流,你可以(如果你需要的话,)。@IlmariKaronen,如果我将流转换为迭代器,我可能会失去并发功能。@AlikElzin kilaka请不要问内存的含义:)对我来说,很明显,没有一个流API解决方案会在任何方面(性能、可读性、维护)超过您提到的简单循环方法(不过我会选择fori循环)。我真的不明白你为什么要在这里使用流。为什么你认为可以安全地使用:
(List)itemIter
?@ernest_k这并不重要,我不知道上下文(什么是
itemIter
)。让我们把它当作一个伪代码,可以编译,只是为了说明这个想法:)好吧,它甚至不编译。由于对象不同时是
Iterable
迭代器
,因此不能将其强制转换为
列表
,并期望它还具有
下一步()
方法。一旦修复了这个问题,就不需要有问题的类型转换:
E firstElement=itemIter.iterator().next();Stream-Stream=StreamSupport.Stream(itemIter.spliterator(),false)我想知道他们是如何实现它的,可能像这里已经给出的解决方案之一。请注意,该方法有一些限制(在文档中提到),并且“主要是为了支持调试而存在”。@Lino combine对第一个元素执行操作(但也使用它)并且在检查完第一个项目后会将其推回,并且您有必要的工具来构造这样的操作。顺便说一下,最好使用
StreamEx.of(itemIter.spliterator())
,这可能会将附加的元信息带到流中(取决于实际的
Iterable
),可能会提高性能。但要求您始终重置该值,并且在多线程环境中不起作用。无法解释为什么这一值不会高出很多:|@Eugene,因为在其他答案后两小时发布,并被置于默认顺序的末尾…无需担心
AtomicReference<Boolean> first = new AtomicReference<Boolean>(Boolean.TRUE);
stream.forEach(e -> 
         System.out.println("First == " + first.getAndUpdate(b -> false)));
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