3.1 从外部迭代到内部迭代

3.1 从外部迭代到内部迭代 - 图1 本章及本书其余部分的例子大多围绕1.3节介绍的案例展开。

Java程序员在使用集合类时,一个通用的模式是在集合上进行迭代,然后处理返回的每一个元素。比如要计算从伦敦来的艺术家的人数,通常代码会写成例3-1这样。

例3-1 使用for循环计算来自伦敦的艺术家人数

  1. int count = 0;
  2. for (Artist artist : allArtists) {
  3. if (artist.isFrom("London")) {
  4. count++;
  5. }
  6. }

尽管这样的操作可行,但存在几个问题。每次迭代集合类时,都需要写很多样板代码。将for循环改造成并行方式运行也很麻烦,需要修改每个for循环才能实现。

此外,上述代码无法流畅传达程序员的意图。for循环的样板代码模糊了代码的本意,程序员必须阅读整个循环体才能理解。若是单一的for循环,倒也问题不大,但面对一个满是循环(尤其是嵌套循环)的庞大代码库时,负担就重了。

就其背后的原理来看,for循环其实是一个封装了迭代的语法糖,我们在这里多花点时间,看看它的工作原理。首先调用iterator方法,产生一个新的Iterator对象,进而控制整个迭代过程,这就是外部迭代。迭代过程通过显式调用Iterator对象的hasNextnext方法完成迭代。展开后的代码如例3-2所示,图3-1展示了迭代过程中的方法调用。

例3-2 使用迭代器计算来自伦敦的艺术家人数

  1. int count = 0;
  2. Iterator<Artist> iterator = allArtists.iterator();
  3. while(iterator.hasNext()) {
  4. Artist artist = iterator.next();
  5. if (artist.isFrom("London")) {
  6. count++;
  7. }
  8. }

3.1 从外部迭代到内部迭代 - 图2

图3-1:外部迭代

然而,外部迭代也有问题。首先,它很难抽象出本章稍后提及的不同操作;此外,它从本质上来讲是一种串行化操作。总体来看,使用for循环会将行为和方法混为一谈。

另一种方法就是内部迭代,如例3-3所示。首先要注意stream()方法的调用,它和例3-2中调用iterator()的作用一样。该方法不是返回一个控制迭代的Iterator对象,而是返回内部迭代中的相应接口:Stream

例3-3 使用内部迭代计算来自伦敦的艺术家人数

  1. long count = allArtists.stream()
  2. .filter(artist -> artist.isFrom("London"))
  3. .count();

图3-2展示了使用类库后的方法调用流程,与图3-1形成对比。

3.1 从外部迭代到内部迭代 - 图3

图3-2:内部迭代

3.1 从外部迭代到内部迭代 - 图4Stream是用函数式编程方式在集合类上进行复杂操作的工具。

例3-3 可被分解为两步更简单的操作:

  • 找出所有来自伦敦的艺术家;
  • 计算他们的人数。

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