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

306
在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)) { ... }

我在这里漏掉了什么吗?


8
除此之外,迭代的另外两种方法(forEach和spliterator)也在Stream中。 - njzk2
1
需要将Stream传递给期望Iterable的旧API,因此需要返回已翻译的文本。 - ZhongYu
14
一个好的集成开发环境(例如IntelliJ)会提示你简化getIterable()函数中的代码,使其变为return s::iterator; - ZhongYu
25
你根本不需要一个方法。当你有一个Stream对象,想要得到一个Iterable对象时,只需传递stream::iterator(或者如果你更喜欢,() -> stream.iterator()),就可以完成了。 - Brian Goetz
8
很不幸,我无法编写for (T element : stream::iterator),所以我仍然希望Stream也能实现Iterable或一个名为toIterable()的方法。 - Thorsten
显示剩余8条评论
9个回答

229

有些人已经在邮件列表上提出了同样的问题on the mailing list☺。主要原因是Iterable还具有可迭代语义,而Stream则没有。

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

如果Stream扩展了Iterable,那么现有的代码可能会感到惊讶,当他们第二次执行for (element : iterable)时,会收到一个抛出ExceptionIterable


31
有趣的是,在Java 7中已经有一些具有这种行为的可迭代对象,例如DirectoryStream:虽然DirectoryStream扩展了Iterable接口,但它不是通用的Iterable,因为它仅支持单个迭代器;调用iterator方法以获取第二个或更多迭代器会抛出IllegalStateException异常。(http://openjdk.java.net/projects/nio/javadoc/java/nio/file/DirectoryStream.html) - roim
41
很遗憾,在Iterable的文档中没有关于iterator是否应该始终可调用或可能不可重复调用的说明。他们应该在文档中加上这个信息。这似乎更像是一个标准实践,而不是正式的规范。 - Lii
7
如果他们要找借口的话,你可能会认为他们至少可以添加一个asIterable()方法,或者对那些只接受Iterable的方法进行重载。 - Hakanai
33
也许最好的解决方案是使Java的foreach能够接受一个Iterable<T>以及可能是一个Stream<T>? - devoured elysium
4
JDK-8148917中提出了一个建议,将流(streams)设为IterableOnce,这可能在未来的Java版本中解决这个问题。 - Nathan Williams
显示剩余7条评论

178

如果要将 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 );

77
您也可以在循环中使用这个 for(X x : (Iterable<X>)stream::iterator),尽管它看起来有些难看。实际上,整个情况都是荒谬的。 - Aleksandr Dubinsky
24
@HRJ IntStream.range(0,N).forEach(System.out::println) - MikeFHay
13
我不理解这个上下文中的双冒号语法。stream::iteratorstream.iterator()之间有什么区别,使得前者可以用于Iterable,而后者却不能? - Daniel C. Sobral
22
回答自己:Iterable是一个函数接口,因此传递一个实现它的函数就足够了。 - Daniel C. Sobral
8
我同意@AleksandrDubinsky的看法,真正的问题在于对于(Iterable<X>)stream :: iterator这样的东西是有一个语义漏洞的解决方法,它虽然能够达到相同的操作目的,但会带来很多代码噪音。但这就是Java,我们已经容忍了List<String> list = new ArrayList(Arrays.asList(array))很长一段时间了 :) 尽管管理层可以直接提供给我们所有 List<String> list = array.toArrayList()。但真正的荒唐之处在于鼓励每个人在Stream上使用forEach,这样必然会产生未经检查的异常[抱怨结束]。 - The Coordinator
显示剩余15条评论

11
你可以在 for 循环中使用 Stream,如下所示:
Stream<T> stream = ...;

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

(Run 这个代码片段)
(这使用了Java 8的函数式接口转换。)
(这在一些评论中已经讨论过,例如Aleksandr Dubinsky,但我想把它提取出来作为一个答案,以使其更加明显。)

4
几乎每个人在看到它编译之前都需要看两遍甚至三遍才能意识到它是如何编译的。这太荒谬了。(这在同一评论流程中的回复中已经涵盖,但我想把评论放在这里以使其更加明显。) - T Tse

11

我想指出,StreamEx确实实现了Iterable(和Stream),以及一系列其他非常棒的功能,这些功能在Stream中缺失。


7
kennytm描述了为什么将Stream视为Iterable是不安全的,Zhong Yu提供了一种解决方法,允许以不安全的方式使用Stream作为Iterable。可以同时实现两者的最佳效果:从Stream获得可重用的Iterable,满足Iterable规范中的所有保证。

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

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

存在一个主要的缺点:

懒惰迭代的好处将会丧失。如果您计划立即在当前线程中迭代所有值,则任何开销都将是微不足道的。但是,如果您只计划部分地或在另一个线程中进行迭代,则此立即和完整的迭代可能会产生意想不到的后果。

当然,最大的优势是您可以重复使用“Iterable”,而“(Iterable<SomeType>) stream::iterator”只允许单次使用。如果接收代码将多次迭代集合,则不仅必要,而且很可能有益于性能。


1
你有尝试编译代码再回答吗?它不起作用。 - Tagir Valeev
1
@TagirValeev 是的,我做了。你需要用适当的类型替换 T。我从可工作的代码中复制了这个例子。 - Zenexer
2
@TagirValeev 我在IntelliJ中进行了再次测试。看起来IntelliJ有时会被这种语法搞糊涂;我还没有真正找到它的模式。然而,代码可以编译成功,并且在编译后IntelliJ会删除错误提示。我想这只是一个bug。 - Zenexer
1
Stream.toArray() 返回一个数组,而不是 Iterable,所以这段代码仍然无法编译。但可能是 Eclipse 中的一个 bug,因为 IntelliJ 似乎可以编译它。 - benez
1
@Zenexer 你是如何将数组分配给Iterable的? - radiantRazor
显示剩余3条评论

3

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

2
如果您不介意使用第三方库,cyclops-react定义了一个实现了Stream和Iterable接口的流,并且还可以回放(解决了kennytm所描述的问题)。请注意保留HTML标签。
 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的首席开发人员]

0

不是完美的,但可以工作:

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

这种方法并不完美,因为它会从流中获取所有项目并将它们放入那个List中,这并不完全符合IterableStream的设计初衷。它们应该是惰性的。


-1

您可以使用Stream<Path>来遍历文件夹中的所有文件,如下所示:

Path path = Paths.get("...");
Stream<Path> files = Files.list(path);

for (Iterator<Path> it = files.iterator(); it.hasNext(); )
{
    Object file = it.next();

    // ...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接