【问题标题】:How do I get the first element while continue streaming?如何在继续流式传输时获取第一个元素?
【发布时间】:2019-07-28 09:10:35
【问题描述】:

我有一系列通用项目。我想打印第一个项目的类名+所有项目的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);
}

我可以使用流 API 在流 (Stream&lt;T&gt;) 上执行此操作吗?

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

【问题讨论】:

  • 请注意,这是一个关于流的问题,而不是关于迭代器的问题。我有一个流——不是迭代器。
  • @AndrewTobilko - 我没有Iterable - 只有Stream
  • 请注意,如果你有一个流,你可以get an iterator(如果你需要,also an iterable)。
  • @IlmariKaronen,如果我将流转换为迭代器,我可能会失去并发能力。

标签: java for-loop collections java-8 java-stream


【解决方案1】:

原生解决方案: Java 中的Stream 不可重用。这意味着,消费流只能完成一次。如果您从流中获取第一个元素,则可以再对其进行一次迭代。

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

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

编辑

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

【讨论】:

  • @AlikElzin-kilaka 请不要询问内存影响 :) 对我来说,很明显没有一个 Stream API 解决方案会超过你提到的简单循环方法(我会选择fori 循环)以任何方式(性能、可读性、维护)。老实说,我不明白你为什么要在这里播放流。
  • 你为什么认为投射是安全的:(List&lt;E&gt;) itemIter
  • @ernest_k 这并不重要,我不知道上下文(itemIter 到底是什么)。让我们把它当作一个可以编译的伪代码,只是为了展示这个想法:)
  • 好吧,它甚至无法编译。由于对象不是IterableIterator 同时,您不能将其转换为List 并期望它也有next() 方法。一旦你解决了这个问题,你就不需要有问题的类型转换:E firstElement = itemIter.iterator().next(); Stream&lt;E&gt; stream = StreamSupport.stream(itemIter.spliterator(), false);
【解决方案2】:

如果您的起点是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 的先前更新。

【讨论】:

  • 不知道为什么这不是更高:|
  • @Eugene 因为在其他答案后两小时发布,并按默认顺序放在最后……没什么好担心的
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

    StreamEx 库扩展了标准 Java 的 Stream API。使用 StreamEx.of(Iterator)peekFirst

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

    【讨论】:

    • 我想知道他们是如何实现它的,可能就像这里已经给出的解决方案之一
    • 请注意,该方法有一些限制(在文档中提到)并且“存在主要是为了支持调试”。
    • @Lino 结合 this answer 对第一个元素执行操作(但也会消耗它)和 this answer 在检查后将第一个元素推回它,你有必要的工具来构造这样的操作。
    • 顺便说一句,最好使用StreamEx.of(itemIter.spliterator()),它可能会向流中携带额外的元信息(取决于实际的Iterable),可能会提高性能。
    【解决方案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);
            }
        })
        ...
    

    【讨论】:

    • 从这个意义上说,我猜即使定义了可迭代对象,有问题的代码也会好得多。请记住,没有像 itemIter.stream() 这样的预先设置。
    • @nullpointer 你是对的,我已经编辑了我的答案以使用 StreamSupport
    • 传递给peek的动作将按照处理顺序执行,不保证是Stream的遇到顺序。
    【解决方案6】:

    你可以滥用减少:

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

    【讨论】:

      【解决方案7】:

      一种解决方法是这样做 -

      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]
      

      【讨论】:

      • 需要你总是重置这个值,并且在多线程环境中不起作用
      猜你喜欢
      • 2023-03-17
      • 1970-01-01
      • 1970-01-01
      • 2022-06-14
      • 2014-07-11
      • 1970-01-01
      • 2013-12-01
      • 2014-10-29
      • 2012-06-22
      相关资源
      最近更新 更多