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

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

Iterator<T> iterator()

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

当我想使用foreach循环遍历一个流时,我必须做如下的事情

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

我是不是遗漏了什么?

44389 次浏览

人们已经问过同样的在邮件列表中☺。主要原因是Iterable也有一个可重复迭代的语义,而Stream没有。

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

如果Stream扩展了Iterable,则现有代码在接收到Iterable抛出Exception时可能会感到惊讶 第二次执行for (element : iterable).

.

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

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

kennytm描述为什么将Stream作为Iterable是不安全的,以及钟宇提出了一个变通办法允许在Iterable中使用Stream,尽管是以不安全的方式。有可能两全其美:从Stream中得到一个可重用的Iterable,它满足Iterable规范所做的所有保证。

注意:SomeType在这里不是一个类型参数——你需要用一个合适的类型替换它(例如,String)或求助于反射

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

它有一个主要的缺点:

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

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

不完美,但可以:

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

这并不完美,因为它将从流中获取所有项并将它们放入List中,这并不是IterableStream的确切含义。它们应该是懒惰的

如果你不介意使用第三方库cyclops-react定义了一个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);

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

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

Stream<T> stream = ...;


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

(运行这个片段)

(这使用了Java 8函数接口强制转换。)

(这在上面的一些评论(例如亚历山大杜宾斯基)中包含,但我想把它拉出来成为一个答案,使它更明显。)

你可以像这样使用Stream<Path>遍历一个文件夹中的所有文件:

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


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


// ...
}

Stream不实现IterableIterable的一般理解是任何可以反复迭代的东西。Stream可能不能重玩。

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

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