【问题标题】:Why does Stream<T> not implement Iterable<T>?为什么 Stream<T> 没有实现 Iterable<T>?
【发布时间】:2013-12-06 10:36:34
【问题描述】:

在 Java 8 中,我们有 Stream<T> 类,奇怪的是它有一个方法

Iterator<T> iterator()

所以你会期望它实现接口Iterable<T>,这正是需要这个方法,但事实并非如此。

当我想使用 foreach 循环遍历 Stream 时,我必须执行类似的操作

public static Iterable<T> getIterable(Stream<T> s) {
    return new Iterable<T> {
        @Override
        public Iterator<T> iterator() {
            return s.iterator();
        }
    };
}

for (T element : getIterable(s)) { ... }

我错过了什么吗?

【问题讨论】:

  • 更不用说iterable的其他2个方法(forEach和spliterator)也在Stream中
  • 这是将Stream 传递给期望Iterable 的旧版API 所必需的
  • 一个好的IDE(例如IntelliJ)会提示你将getIterable()中的代码简化为return s::iterator;
  • 你根本不需要方法。如果你有一个 Stream,并且想要一个 Iterable,只需传递 stream::iterator(或者,如果你愿意,() -> stream.iterator()),你就完成了。
  • 不幸的是我不能写for (T element : stream::iterator),所以我仍然希望Stream也可以实现Iterable或方法toIterable()

标签: java java-8 java-stream iterable


【解决方案1】:

人们已经问过同样的问题on the mailing list ☺。主要原因是Iterable也有可重迭代的语义,而Stream没有。

我认为主要原因是Iterable 意味着可重用性,而Stream 只能使用一次——更像是Iterator

如果Stream 扩展了Iterable,那么当现有代码接收到抛出ExceptionIterable 时,它可能会感到惊讶 他们第二次这样做for (element : iterable)

【讨论】:

  • 奇怪的是,在 Java 7 中已经有一些具有这种行为的可迭代对象,例如DirectoryStream:虽然 DirectoryStream 扩展了 Iterable,但它不是通用的 Iterable,因为它只支持单个 Iterator;调用迭代器方法来获取第二个或后续迭代器会引发 IllegalStateException。 (openjdk.java.net/projects/nio/javadoc/java/nio/file/…)
  • 不幸的是,Iterable 的文档中没有关于 iterator 是否应该始终或可能不可以多次调用的文档。那是他们应该放在那里的东西。这似乎更像是一种标准做法,而不是正式规范。
  • 如果他们要找借口,你会认为他们至少可以添加一个 asIterable() 方法——或者重载所有只接受 Iterable 的方法。
  • 也许最好的解决方案是让 Java 的 foreach 接受 Iterable 以及可能的 Stream
  • @Lii 按照标准做法,这是一个非常强大的做法。
【解决方案2】:

要将Stream 转换为Iterable,您可以这样做

Stream<X> stream = null;
Iterable<X> iterable = stream::iterator

要将Stream 传递给需要Iterable 的方法,

void foo(Iterable<X> iterable)

简单

foo(stream::iterator) 

但它可能看起来很有趣;说得清楚一点可能会更好

foo( (Iterable<X>)stream::iterator );

【讨论】:

  • 你也可以在循环中使用它for(X x : (Iterable&lt;X&gt;)stream::iterator),虽然它看起来很丑。真的,整个情况很荒谬。
  • @HRJ IntStream.range(0,N).forEach(System.out::println)
  • 我不明白这种情况下的双冒号语法。 stream::iteratorstream.iterator() 之间有什么区别,这使得前者可以被 Iterable 接受,但不能被后者接受?
  • 回答我自己:Iterable 是一个函数式接口,所以传递一个实现它的函数就足够了。
  • 我必须同意@AleksandrDubinsky,真正的问题是(X x : (Iterable)stream::iterator) 有一个语义解决方法,尽管它只允许相同的操作在一个很大的代码噪音。但是这是 Java,我们已经容忍 List list = new ArrayList(Arrays.asList(array)) 很长一段时间了:) 尽管它的力量可以为我们提供所有 List list = array。到数组列表()。但真正的荒谬之处在于,通过鼓励每个人在 Stream 上使用 forEach,必须取消检查异常 [咆哮结束]。
【解决方案3】:

您可以在 for 循环中使用 Stream,如下所示:

Stream<T> stream = ...;

for (T x : (Iterable<T>) stream::iterator) {
    ...
}

(运行this snippet here

(这使用 Java 8 功能接口转换。)

(上面的一些 cmets 对此进行了介绍(例如 Aleksandr Dubinsky),但我想将其提取到答案中以使其更明显。)

【讨论】:

  • 几乎每个人都需要查看它两次或三次才能意识到它是如何编译的。这是荒谬的。 (这在对同一评论流中的 cmets 的响应中有所涉及,但我想在此处添加评论以使其更加可见。)
【解决方案4】:

我想指出StreamEx 确实实现了Iterable(和Stream),以及Stream 中缺少的许多其他非常棒的功能。

【讨论】:

    【解决方案5】:

    kennytm described 为什么将Stream 视为Iterable 是不安全的,而Zhong Yu offered a workaround 允许在Iterable 中使用Stream,尽管是以不安全的方式。可以两全其美:Stream 中的可重用Iterable 满足Iterable 规范的所有保证。

    注意:SomeType 在这里不是类型参数——您需要将其替换为适当的类型(例如,String)或诉诸反射

    Stream<SomeType> stream = ...;
    Iterable<SomeType> iterable = stream.collect(toList()):
    

    有一个主要缺点:

    惰性迭代的好处将会丧失。如果您计划立即迭代当前线程中的所有值,则任何开销都可以忽略不计。但是,如果您计划只进行部分迭代或在不同线程中进行迭代,那么这种直接且完整的迭代可能会产生意想不到的后果。

    当然,最大的优势是您可以重复使用Iterable,而(Iterable&lt;SomeType&gt;) stream::iterator 只允许一次使用。如果接收代码将多次迭代集合,这不仅是必要的,而且可能对性能有益。

    【讨论】:

    • 您在回答之前尝试过编译代码吗?它不起作用。
    • @TagirValeev 是的,我做到了。您需要将 T 替换为适当的类型。我从工作代码中复制了示例。
    • @TagirValeev 我在 IntelliJ 中再次对其进行了测试。似乎 IntelliJ 有时会被这种语法弄糊涂。我还没有真正找到它的模式。但是,代码编译良好,并且 IntelliJ 会在编译后删除错误通知。我想这只是一个错误。
    • Stream.toArray() 返回一个数组,而不是 Iterable,所以这段代码仍然无法编译。但它可能是 eclipse 中的一个错误,因为 IntelliJ 似乎可以编译它
    • @Zenexer 你是如何将数组分配给 Iterable 的?
    【解决方案6】:

    Stream 没有实现Iterable。对Iterable 的一般理解是可以反复迭代的任何东西,通常是一次又一次。 Stream 可能无法重播。

    我能想到的唯一解决方法是重新创建流,基于流的迭代也是可重放的。我在下面使用Supplier 来创建一个新的流实例,每次创建一个新的迭代器。

        Supplier<Stream<Integer>> streamSupplier = () -> Stream.of(10);
        Iterable<Integer> iterable = () -> streamSupplier.get().iterator();
        for(int i : iterable) {
            System.out.println(i);
        }
        // Can iterate again
        for(int i : iterable) {
            System.out.println(i);
        }
    

    【讨论】:

      【解决方案7】:

      如果您不介意使用第三方库 cyclops-react 定义了一个 Stream,它实现了 Stream 和 Iterable 并且也是可重放的(解决问题 kennytm described)。

       Stream<String> stream = ReactiveSeq.of("hello","world")
                                          .map(s->"prefix-"+s);
      

      或:-

       Iterable<String> stream = ReactiveSeq.of("hello","world")
                                            .map(s->"prefix-"+s);
      
       stream.forEach(System.out::println);
       stream.forEach(System.out::println);
      

      [披露我是 cyclops-react 的主要开发者]

      【讨论】:

        【解决方案8】:

        不完美,但会起作用:

        iterable = stream.collect(Collectors.toList());
        

        并不完美,因为它会从流中获取所有项目并将它们放入 List,这与 IterableStream 的内容不完全一样。他们应该是lazy

        【讨论】:

          【解决方案9】:

          您可以像这样使用Stream&lt;Path&gt; 遍历文件夹中的所有文件:

          Path path = Paths.get("...");
          Stream<Path> files = Files.list(path);
          
          for (Iterator<Path> it = files.iterator(); it.hasNext(); )
          {
              Object file = it.next();
          
              // ...
          }
          

          【讨论】:

            猜你喜欢
            • 2016-06-26
            • 2012-08-04
            • 1970-01-01
            • 1970-01-01
            • 2011-06-14
            • 2014-05-31
            • 1970-01-01
            相关资源
            最近更新 更多