为什么Stream <T>没有实现Iterable <T>?

roi*_*oim 254 java iterable java-8 java-stream

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

Iterator<T> iterator()
Run Code Online (Sandbox Code Playgroud)

所以你会期望它实现接口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)) { ... }
Run Code Online (Sandbox Code Playgroud)

我在这里错过了什么吗?

ken*_*ytm 189

已经有人在邮件列表上问过同样的问题 ☺.主要原因是Iterable也有一个可重复的语义,而Stream则没有.

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

如果Stream扩展了,Iterable那么现有的代码在收到第二次Iterable抛出的时候可能会感到惊讶.Exceptionfor (element : iterable)

  • 不幸的是,没有什么是关于`iterator`是否应该总是或可能不会多次调用的`Iterable'的文档.这是他们应该放在那里的东西.这似乎更像是标准实践,而不是正式规范. (30认同)
  • 也许最好的解决方案是让Java的foreach接受Iterable <T>以及可能的Stream <T>? (23认同)
  • 奇怪的是,在Java 7中已经存在一些具有此行为的迭代,例如DirectoryStream:虽然DirectoryStream扩展了Iterable,但它不是通用的Iterable,因为它只支持单个Iterator; 调用iterator方法来获取第二个或后续的迭代器会抛出IllegalStateException.(http://openjdk.java.net/projects/nio/javadoc/java/nio/file/DirectoryStream.html) (20认同)
  • 如果他们要使用借口,你会认为他们至少可以添加一个asIterable()方法 - 或者对所有那些只采用Iterable的方法进行重载. (7认同)
  • @Lii作为标准实践,它虽然非常强大. (3认同)
  • [JDK-8148917](https://bugs.openjdk.java.net/browse/JDK-8148917?focusedCommentId=14248575&amp;page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-14248575)包含提议制作流`IterableOnce`,这可能会在未来的 Java 版本中解决这个问题。 (3认同)
  • @devouredelysium IMO它应该接受 Iterable&lt;T&gt; 和 Iterator&lt;T&gt; (2认同)
  • 更多证据表明 Java 刚刚成为一个组装不良的杂物。为了与更现代的语言竞争,有很多不成熟的功能,但它们都达不到要求。 (2认同)

Zho*_*gYu 153

要转换StreamIterable,你可以做

Stream<X> stream = null;
Iterable<X> iterable = stream::iterator
Run Code Online (Sandbox Code Playgroud)

要传递Stream给期望的方法Iterable,

void foo(Iterable<X> iterable)
Run Code Online (Sandbox Code Playgroud)

只是

foo(stream::iterator) 
Run Code Online (Sandbox Code Playgroud)

但它可能看起来很有趣; 更明确一点可能会更好

foo( (Iterable<X>)stream::iterator );
Run Code Online (Sandbox Code Playgroud)

  • 你也可以在循环`for(X x :( Iterable <X>)stream :: iterator)中使用它,虽然它看起来很难看.真的,整个情况都很荒谬. (61认同)
  • @HRJ`IntStream.range(0,N).forEach(System.out :: println)` (24认同)
  • 回答自己:"Iterable"是一个功能接口,所以通过一个实现它的函数就足够了. (18认同)
  • 我不明白这个上下文中的双冒号语法.`stream :: iterator`和`stream.iterator()`之间有什么区别,这使得前者可以接受`Iterable'而不是后者? (10认同)
  • 在Scala中我可以做:`(0到N)foreach println`在Java 8中同样的事情是:`for(int i :( Iterable <Integer>)IntStream.range(0,N):: iterator){System. out.println(i)}` (4认同)
  • 我必须同意@AleksandrDubinsky,真正的问题是(X x :( Iterable <X>)stream :: iterator)有一个语义解决方法,它只允许相同的操作,尽管代码噪声很大.但是这是Java,我们已经容忍了List <String> list = new ArrayList(Arrays.asList(array))很长一段时间:)虽然功能可以为我们提供所有List <String> list = array. toArrayList().但真正荒谬的是,通过鼓励每个人使用forEach on Stream,**例外必须不受限制**[咆哮结束]. (4认同)
  • 值得注意的是,如果接收代码试图重新使用Iterable(例如,两个单独的for-each循环),这将会中断[kennytm的答案](http://stackoverflow.com/a/20130131/1188377) .这在技术上破坏了规范.您只能使用一次流; 你不能两次调用BaseStream :: iterator.第一个呼叫终止流.根据JavaDoc:"这是终端操作." 虽然这种解决方法很方便,但您不应将生成的Iterable传递给不受您最终控制的代码. (4认同)
  • 希望他们能在java9中修复它们.HRJ.希望. (2认同)
  • @wegry AFAIK `forEachOrdered()` 仅在流本身是并行的情况下才需要,在这种情况下不是。 (2认同)
  • 可迭代重用问题仅存在,因为方法引用捕获了流实例。您可以通过在 lambda 中生成流来避免该问题:`Iterable&lt;X&gt; iterable = () -&gt; source.stream().iterator();` (2认同)
  • @Ben - 请记住,`::iterator` 不是迭代器,它是一个返回迭代器的函数。 (2认同)

Ale*_*sky 10

我想指出的是,StreamEx实现Iterable(和Stream),还有许多其他非常棒的功能Stream.


Zen*_*xer 7

kennytm描述了为什么将a Stream作为一种处理是不安全的Iterable,而钟宇提供了一种允许使用Streamas 的解决方法Iterable,尽管是以不安全的方式.这是有可能得到两全其美:可再用IterableStream满足所做出的一切保障Iterable规范.

注意:SomeType这里不是类型参数 - 您需要用适当的类型(例如String)替换它或使用反射

Stream<SomeType> stream = ...;
Iterable<SomeType> iterable = stream.collect(toList()):
Run Code Online (Sandbox Code Playgroud)

有一个主要缺点:

延迟迭代的好处将会丢失.如果您计划立即迭代当前线程中的所有值,则任何开销都可以忽略不计.但是,如果您计划仅部分迭代或在不同的线程中进行迭代,则此即时且完整的迭代可能会产生意想不到的后果.

当然,最大的优点是你可以重复使用Iterable,而(Iterable<SomeType>) stream::iterator只允许一次使用.如果接收代码将多次遍历集合,则这不仅是必要的,而且可能有益于性能.

  • @TagirValeev我在IntelliJ中再次测试了它.似乎IntelliJ有时会被这种语法搞糊涂了; 我还没有真正找到它的模式.但是,代码编译良好,IntelliJ在编译后删除错误通知.我想这只是一个bug. (2认同)

Ric*_*ich 6

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

Stream<T> stream = ...;

for (T x : (Iterable<T>) stream::iterator) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

在此处运行此代码段

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

(这在上面的一些评论中得到了介绍(例如Aleksandr Dubinsky,但是我想将其提出来以使其更清晰可见)。

  • 几乎每个人都需要看两遍或三次才能意识到它是如何编译的。这是荒谬的。(这在同一评论流中对评论的回复中有所介绍,但我想在此处添加评论以使其更加明显。) (2认同)