3.1 从外部迭代到内部迭代
本章及本书其余部分的例子大多围绕1.3节介绍的案例展开。
Java程序员在使用集合类时,一个通用的模式是在集合上进行迭代,然后处理返回的每一个元素。比如要计算从伦敦来的艺术家的人数,通常代码会写成例3-1这样。
例3-1 使用for循环计算来自伦敦的艺术家人数
int count = 0;for (Artist artist : allArtists) {if (artist.isFrom("London")) {count++;}}
尽管这样的操作可行,但存在几个问题。每次迭代集合类时,都需要写很多样板代码。将for循环改造成并行方式运行也很麻烦,需要修改每个for循环才能实现。
此外,上述代码无法流畅传达程序员的意图。for循环的样板代码模糊了代码的本意,程序员必须阅读整个循环体才能理解。若是单一的for循环,倒也问题不大,但面对一个满是循环(尤其是嵌套循环)的庞大代码库时,负担就重了。
就其背后的原理来看,for循环其实是一个封装了迭代的语法糖,我们在这里多花点时间,看看它的工作原理。首先调用iterator方法,产生一个新的Iterator对象,进而控制整个迭代过程,这就是外部迭代。迭代过程通过显式调用Iterator对象的hasNext和next方法完成迭代。展开后的代码如例3-2所示,图3-1展示了迭代过程中的方法调用。
例3-2 使用迭代器计算来自伦敦的艺术家人数
int count = 0;Iterator<Artist> iterator = allArtists.iterator();while(iterator.hasNext()) {Artist artist = iterator.next();if (artist.isFrom("London")) {count++;}}

图3-1:外部迭代
然而,外部迭代也有问题。首先,它很难抽象出本章稍后提及的不同操作;此外,它从本质上来讲是一种串行化操作。总体来看,使用for循环会将行为和方法混为一谈。
另一种方法就是内部迭代,如例3-3所示。首先要注意stream()方法的调用,它和例3-2中调用iterator()的作用一样。该方法不是返回一个控制迭代的Iterator对象,而是返回内部迭代中的相应接口:Stream。
例3-3 使用内部迭代计算来自伦敦的艺术家人数
long count = allArtists.stream().filter(artist -> artist.isFrom("London")).count();
图3-2展示了使用类库后的方法调用流程,与图3-1形成对比。
图3-2:内部迭代

Stream是用函数式编程方式在集合类上进行复杂操作的工具。
例3-3 可被分解为两步更简单的操作:
- 找出所有来自伦敦的艺术家;
- 计算他们的人数。
每种操作都对应Stream接口的一个方法。为了找出来自伦敦的艺术家,需要对Stream对象进行过滤:filter。过滤在这里是指“只保留通过某项测试的对象”。测试由一个函数完成,根据艺术家是否来自伦敦,该函数返回true或者false。由于Stream API的函数式编程风格,我们并没有改变集合的内容,而是描述出Stream里的内容。count()方法计算给定Stream里包含多少个对象。
