3.2 实现机制

例3-3中,整个过程被分解为两种更简单的操作:过滤和计数,看似有化简为繁之嫌——例3-1中只含一个for循环,两种操作是否意味着需要两次循环?事实上,类库设计精妙,只需对艺术家列表迭代一次。

通常,在Java中调用一个方法,计算机会随即执行操作:比如,System.out.println("Hello World");会在终端上输出一条信息。Stream里的一些方法却略有不同,它们虽是普通的Java方法,但返回的Stream对象却不是一个新集合,而是创建新集合的配方。现在,尝试思考一下例3-4中代码的作用,一时毫无头绪也没关系,稍后会详细解释。

例3-4 只过滤,不计数

  1. allArtists.stream()
  2. .filter(artist -> artist.isFrom("London"));

这行代码并未做什么实际性的工作,filter只刻画出了Stream,但没有产生新的集合。像filter这样只描述Stream,最终不产生新集合的方法叫作惰性求值方法;而像count这样最终会从Stream产生值的方法叫作及早求值方法

如果在过滤器中加入一条println语句,来输出艺术家的名字,就能轻而易举地看出其中的不同。例3-5对例3-4作了一些修改,加入了输出语句。运行这段代码,程序不会输出任何信息!

例3-5 由于使用了惰性求值,没有输出艺术家的名字

  1. allArtists.stream()
  2. .filter(artist -> {
  3. System.out.println(artist.getName());
  4. return artist.isFrom("London");
  5. });

如果将同样的输出语句加入一个拥有终止操作的流,如例3-3中的计数操作,艺术家的名字就会被输出(见例3-6)。

例3-6 输出艺术家的名字

  1. long count = allArtists.stream()
  2. .filter(artist -> {
  3. System.out.println(artist.getName());
  4. return artist.isFrom("London");
  5. })
  6. .count();

以披头士乐队的成员作为艺术家列表,运行上述程序,命令行里输出的内容如例3-7所示。

例3-7 显示披头士乐队成员名单的示例输出

  1. John Lennon
  2. Paul McCartney
  3. George Harrison
  4. Ringo Starr

判断一个操作是惰性求值还是及早求值很简单:只需看它的返回值。如果返回值是Stream,那么是惰性求值;如果返回值是另一个值或为空,那么就是及早求值。使用这些操作的理想方式就是形成一个惰性求值的链,最后用一个及早求值的操作返回想要的结果,这正是它的合理之处。计数的示例也是这样运行的,但这只是最简单的情况:只含两步操作。

整个过程和建造者模式有共通之处。建造者模式使用一系列操作设置属性和配置,最后调用一个build方法,这时,对象才被真正创建。

读者一定会问:“为什么要区分惰性求值和及早求值?”只有在对需要什么样的结果和操作有了更多了解之后,才能更有效率地进行计算。例如,如果要找出大于10的第一个数字,那么并不需要和所有元素去做比较,只要找出第一个匹配的元素就够了。这也意味着可以在集合类上级联多种操作,但迭代只需一次。