「Java 8 函数式编程」读书笔记——流

本文是「Java 8 函数式编程」第三章的读书笔记,章名为流。本章主要介绍了外部迭代与内部迭代以及常用的高阶函数。

外部迭代与内部迭代

外部迭代

过去我们要对一个List进行迭代时,往往会采用如下方式:

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

而这种方法的原理,其实是先调用iterator方法,然后再迭代,等效于如下代码:

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

这样的迭代方式,把迭代的控制权交给了iterator对象,让其控制整个迭代过程,这就叫做外部迭代

外部迭代需要我们自己编写迭代的控制代码,显得十分繁琐。特别是对于Map对象,繁琐到我都不想给出例子。

外部迭代将行为和方法混为一谈,难以对代码进行重构操作。

内部迭代

与之相对的就是内部迭代了。内部迭代就是把迭代的控制权交给了集合本身,让集合自己实现相应的迭代,而调用者并不需要关心如何迭代

要使用内部迭代,需要使用Java 8中新增的接口Stream。而集合框架都已经包含了一个stream()方法,用于获得Stream对象。

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

这个例子就是使用的内部迭代。先获取stream对象,然后调用filter方法过滤,最后统计符合条件的个数。

实现机制

Java中调用一个方法,通常会立即执行操作。然而Stream里的一些方法却不太一样,它们返回的对象不是新的集合,而是创建新集合的配方。我们通过一个例子说明:

1
2
3
4
5
6
7
Stream<String> names = Stream.of("Bryant", "Jordon", "James")
.filter(name -> {
System.out.println(name);
return name.length() == 6;
});
System.out.println("counting");
System.out.println(names.count());

最终会得到如下输出:

1
2
3
4
5
counting
Bryant
Jordon
James
2

出现这样的结果,原因是

  • filter这样的方法,只会描述Stream,最终不会产生新集合的方法叫做惰性求值方法
  • count这样会从Stream中产生值或集合等结果的方法叫做及早求值方法

判断一个操作是惰性求值还是及早求值,只需要看它的返回值

  • 如果返回值是Stream,则是惰性求值
  • 返回的是一个值或null,则是及早求值

在对集合使用流操作时,使用惰性求值方法形成一个惰性求值的链,最后用及早求值方法得到结果,而集合只需要迭代一次。

常用流操作

  • collect:及早求值,常用于生成List Map或其他复杂的数据结构
  • map:惰性求值,将一种类型的数据转换成另一种类型,将一个流中的值转化成一个新的流,类似于Hadoop里的map
  • filter:惰性求值,过滤不符合条件的元素
  • flatMap:惰性求值,类似于map,只是Function参数的返回值限定为Stream,用于连接多个Stream成为一个Stream
  • max & min:及早求值,reduce方法的特例,返回Optional(第四章介绍)对象
  • reduce:及早求值,从一组值中生成一个值,类似于Hadoop中的reduce

高阶函数

高阶函数 :point_right: 接收一个函数作为参数,或者返回一个函数的函数。

正确使用Lambda表达式

  • 明确要达成什么转化,而不是说明如何转化
  • 没有副作用:
    • 只通过函数的返回值就能充分理解函数的全部作用
    • 函数不会修改程序或外界的状态
    • 获取值而不是变量(避免使用数组逃过JVM的追杀,应该考虑优化逻辑)