为什么Stream <T>不实现Iterable <T>?


261

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

我在这里想念什么吗?


7
更不用说其他两种可迭代的方法(forEach和spliterator)也在Stream中
njzk2 2013年

1
这需要传递Stream给预期的旧版APIIterable
ZhongYuu

11
良好的IDE(例如IntelliJ)将提示您简化代码getIterable()return s::iterator;
ZhongYuu'13

23
您根本不需要任何方法。如果您有一个Stream,并且想要一个Iterable,只需传递stream :: iterator(或者,如果愿意,可以传递()-> stream.iterator()),就可以完成了。
Brian Goetz 2014年

6
不幸的是我不能写for (T element : stream::iterator),所以我还是希望Stream也可以实现Iterable或方法toIterable()
托尔斯滕2015年

Answers:


197

人们已经在邮件列表 asked 问过同样的问题。主要原因是Iterable也具有可重复的语义,而Stream没有。

我认为主要原因是Iterable暗示可重用性,而Stream它只能使用一次,更像是Iterator

如果Stream扩展Iterable当它接收到那么现有的代码可能会感到惊讶Iterable的是抛出一个Exception他们做第二次for (element : iterable)


22
奇怪的是,在Java 7中已经有一些可迭代的行为,例如DirectoryStream:尽管DirectoryStream扩展了Iterable,但它不是通用的Iterable,因为它仅支持单个Iterator;调用迭代器方法以获得第二个或后续迭代器,则抛出IllegalStateException。(openjdk.java.net/projects/nio/javadoc/java/nio/file/...
roim

31
不幸的是,没有Iterable关于是否iterator应该多次调用或多次调用的文档。那是他们应该放在那里的东西。这似乎更像是标准实践,而不是正式规范。
2014年

7
如果他们要使用借口,您可能会认为他们至少可以添加一个asIterable()方法-或所有仅采用Iterable的方法的重载。
Trejkaz 2014年

25
也许最好的解决方案是让Java的foreach接受Iterable <T>以及潜在地接受Stream <T>?
吞噬了极乐世界

3
@Lii按照标准惯例,这是一个相当强大的做法。
biziclop,2015年

160

要将转换StreamIterable,您可以执行

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

要将a传递Stream给期望的方法Iterable

void foo(Iterable<X> iterable)

只是

foo(stream::iterator) 

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

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

66
您也可以在循环中使用for(X x : (Iterable<X>)stream::iterator)它,尽管看起来很丑。真的,整个情况是荒谬的。
Aleksandr Dubinsky

24
@HRJIntStream.range(0,N).forEach(System.out::println)
MikeFHay

11
在这种情况下,我不了解双冒号语法。stream::iterator和之间有什么区别stream.iterator(),从而使前者可以接受,Iterable但后者不能接受?
Daniel C. Sobral 2014年

20
回答自己:Iterable是一个功能接口,因此传递一个实现它的功能就足够了。
Daniel C. Sobral 2014年

5
值得注意的是,如果接收代码尝试根据kennytm的答案重用Iterable(例如,两个单独的for-each循环),则此方法将中断。从技术上讲这违反了规范。您只能使用一次流。您不能两次调用BaseStream :: iterator。第一次调用将终止流。根据JavaDoc:“这是一个终端操作。” 尽管此解决方法方便,但您不应将最终的Iterable传递给不受最终控制的代码。
Zenexer


8

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

Stream<T> stream = ...;

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

在此处运行此代码段

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

(这在上面的一些评论中得到了介绍(例如Aleksandr Dubinsky,但我想将其引入答案中以使其更加明显。)


几乎每个人在意识到它如何编译之前都需要仔细研究两次或三次。这很荒谬。(在同一评论流中对评论的回复中对此进行了介绍,但我想在此处添加评论以使其更加可见。)
ShioT

7

kennytm描述了将a Stream视为不安全的原因IterableZhong Yu提供了一种允许使用Streamas 的变通方法Iterable,尽管这种方式不安全。这是有可能得到两全其美:可再用IterableStream满足全部由作出的保证Iterable规范。

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

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

有一个主要的缺点:

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

当然,最大的好处是您可以重复使用Iterable,而(Iterable<SomeType>) stream::iterator只允许一次使用。如果接收代码将遍历集合多次,则这不仅是必要的,而且可能对性能有所帮助。


1
您是否曾尝试在回答之前编译代码?没用
塔吉尔·瓦列夫

1
@TagirValeev是的,我做到了。您需要用适当的类型替换T。我从工作代码中复制了示例。
Zenexer '16

2
@TagirValeev我再次在IntelliJ中对其进行了测试。似乎IntelliJ有时会被这种语法弄糊涂。我还没有真正找到一种模式。但是,代码可以正常编译,并且IntelliJ可以在编译后删除错误通知。我想这只是一个错误。
Zenexer '16

1
Stream.toArray()返回一个数组,而不是Iterable,因此此代码仍无法编译。但这可能是eclipse中的错误,因为IntelliJ似乎可以对其进行编译
benez

1
@Zenexer如何将数组分配给Iterable?
radiantRazor

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,该Stream同时实现Stream和Iterable并且也可重播(解决了所描述的问题kennytm)。

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

[我是独眼巨人反应的主要开发者]


0

不完美,但可以工作:

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

这不是完美的方法,因为它将从流中获取所有项目并将其放入List,这Iterable与目标和目标不完全相同Stream。他们应该是懒惰的


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

    // ...
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.