1. 概述

Java 8 引入了流(Stream) API,它提供了一种处理元素序列的功能。流API支持在对象集合(如列表、集合)上进行操作链式处理,以达到期望的结果。

本教程将探讨如何将流用作可迭代的序列。

2. 可迭代与迭代器

Iterable<T> 接口自Java 1.5起可用。实现此接口的类允许其对象成为for-each循环的目标。实现类不存储任何关于迭代状态的信息,并应生成有效的迭代器。

Collection接口扩展了Iterable接口,所有Collection接口的具体实现,如ArrayListHashSet,通过实现Iterableiterator()方法来产生迭代器。

Iterator<T> 接口也是Java集合框架的一部分,自Java 1.2起可用。实现Iterator<T>的类必须提供遍历集合的方法,例如移动到下一个元素、检查是否有更多元素或从集合中删除当前元素:

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}

3. 问题陈述

现在我们了解了迭代器和可迭代接口的基础知识以及它们的作用,接下来理解问题的核心。

实现了Collection接口的类本质上也实现了Iterable<T>接口。然而,流有一些不同。值得注意的是,流Stream<T>扩展的基流接口BaseStream<T>iterator()方法,但并未实现Iterable接口。

这个限制带来了一个挑战:无法直接在流上使用增强的for-each循环。

接下来的章节我们将探讨一些解决这个问题的方法,并最终讨论为什么流不同于集合,不扩展Iterable接口的原因。

4. 使用iterator()方法将流转换为可迭代

流接口的iterator()方法返回流中元素的迭代器。这是一个终端流操作:

Iterator<T> iterator();

然而,我们仍然不能直接使用这个迭代器在增强的for-each循环中:

private void streamIterator(List<String> listOfStrings) {
    Stream<String> stringStream = listOfStrings.stream();
    // this does not compile
    for (String eachString : stringStream.iterator()) {
        doSomethingOnString(eachString);
    }
}

for-each循环适用于可迭代对象,而不是迭代器。为了解决这个问题,我们需要将迭代器转换为Iterable实例,然后应用所需的for-each循环。由于Iterable<T>是一个函数式接口,我们可以使用lambda表达式编写代码:

for (String eachString : (Iterable<String>) () -> stringStream.iterator()) {
    doSomethingOnString(eachString);
}

利用方法引用的方式,我们可以进一步重构代码:

for (String eachString : (Iterable<String>) stringStream::iterator) {
    doSomethingOnString(eachString.toLowerCase());
}

也可以使用临时变量iterableStream先持有Iterable,然后再在for-each循环中使用:

Iterable<String> iterableStream = () -> stringStream.iterator();
for (String eachString : iterableStream) {
    doSomethingOnString(eachString, sentence);
}

5. 将流转换为for-each循环中的可迭代

我们之前讨论过Collection接口继承自Iterable接口。因此,我们可以将给定的流转换为集合,并将其作为可迭代对象使用:

for(String eachString : stringStream.collect(Collectors.toList())) {
    doSomethingOnString(eachString);
}

6. 为何流不实现Iterable

我们已经看到如何使用流作为可迭代对象。列表和集合等数据结构通常会存储数据并在其生命周期中多次使用。这些对象会被传递给不同的方法,多次改变,并且最重要的是,它们会被反复遍历。

相反,流是一种一次性使用的数据结构,因此并不设计用于使用for-each循环进行迭代。流通常不期望被反复遍历,当流已关闭并进行操作时,会抛出IllegalStateException。因此,尽管流提供了iterator()方法,但它并不扩展Iterable接口。

7. 总结

在这篇文章中,我们探讨了流作为可迭代序列的不同使用方式。

我们简要讨论了可迭代和迭代器的区别,以及为什么Stream<T>不实现Iterable<T>接口。

如往常一样,所有的代码示例可以在GitHub上找到。