「Java 8 函数式编程」读书笔记——高级集合类和收集器

本章是该书的第五章, 主要讲了方法引用和收集器

方法引用

形如:

artist -> artist.getName()
(String arg) -> arg.length()

这样的表达式, 可以简写为:

Artist::getName
String::length

这种简写的语法被称为方法引用. 方法引用无需考虑参数, 因为一个方法引用可以在不同的情况下解析为不同的Lambda表达式, 这依赖于JVM的推断.

方法引用的类型

方法引用可以分为四类:

  • 引用静态方法: ClassName::staticMethodName, 比如: String.valueOf

  • 引用特定实例方法: object::instanceMethodName, 比如: str::toString

  • 引用特定类型的任意对象的实例方法: ClassName::instanceMethodName, 比如: String::length

  • 引用构造方法: ClassName::new, 比如: String::new

元素顺序

当我们对集合进行操作时, 有时希望是按照一定的顺序来操作, 而有时又希望是乱序的操作. 有两个方法可以帮助我们进行顺序的操作.

乱序

BaseStream.unordered()方法可以打乱顺序, 科技将本来有序的集合变成无序的集合

排序

Stream.sorted方法有两个签名, 一个无参, 一个有参数Comparator comparator

  • 无参的方法要求T实现了Comparable接口

  • 有参方法需要提供一个比较器

收集器

收集器是一种通用的, 从流中生成复杂值的结构. 将其传给collect方法, 所有的流就都可以使用它. 而下面提到的单个收集器, 都可以使用reduce方法模拟.

转换成集合

我们可以使用Collectors中的静态方法toList() toSet()等, 将流收集为ListSet

stream.collect(toList())
stream.collect(toSet())

我们不需要关心具体使用的是哪一种具体的实现, Stream类库会为我们选择. 因为我们可以利用Stream进行并行数据处理, 所以选择是否线程安全的集合十分重要.

当然我们也可以指定使用哪一种实现来进行收集:

stream.collect(toCollection(ArrayList::new))

转换成值

Collectors类提供了很多的方法用于转化值, 比如counting maxBy minBy等等, 可以查看javadoc了解.

目前了解到的是, 这三个方法都可以使用Stream中的count max min方法代替, 而不需要作为collect方法的参数

数据分割

有时我们想按照一个条件把数据分成两个部分, 而不是只获取符合条件的部分, 这时可以使用partitioningBy方法收集. 将它传入collect方法, 可以得到一个Map, 然后就可以对相应的数据进行处理了.

数据分组

groupingBy方法可以将流分成多个List, 而不仅仅是两个, 接收一个Lambda表达式作为参数, 其返回值作为key, 最后的结果也是一个Map, 形如Map. 这一方法类似于SQL中的group by

生成字符串

如果要从流中得到字符串, 可以在得到Stream之后使用Collectors.joining方法收集. 该方法接收3个String参数, 分别是分隔符 前缀 后缀

artists.stream()
  .map(Artist::getName)
  .collect(Collectors.joining(",", "[", "]"));

组合收集器

我们可以将收集器组合起来, 达到更强的功能. 书上举了两个栗子

  • 例一

public Map numberOfAlbums(Stream albums) {
  return albums
    .collect(
    groupingBy(Album::getMainMusicina, counting()));
}

这个方法的目的是统计每个歌手的作品数目. 如果不组合收集器, 我们先用groupingBy得到一个Map>之后, 还要去遍历Map得到统计数目, 增加了代码量和性能开销.

上面的counting方法类似于count方法, 作用于List的流上.

  • 例二

public Map> nameOfAlbums(Stream albums) {
  return albums
    .collect(
    groupingBy(Album::getMainMusician,
              mapping(Album::getName, toList())));
}

这个方法的目的是得到每个歌手的作品名称列表. 如果不组合收集器, 我们将会先得到一个Map>. 然而, 我们只想得到作品名称, 也就是一个List, 组合mapping收集器可以帮助我们实现效果.

mapping收集器的功能类似于map, 将一种类型的流转换成另一种类型. 所以类似的, mapping并不知道要把结果收集成什么数据结构, 它的第二个参数就会接收一个普通的收集器, 比如这里的toList, 来完成收集.

这里的countingmapping是我们用到的第二个收集器, 用于收集最终结果的一个子集, 这些收集器叫做下游收集器.

定制收集器

定制收集器看起来麻烦, 其实抓住要点就行了.

使用reduce方法

前面说过, 这些收集器都可以使用reduce方法实现, 我们定制收集器, 实际上就是为reduce方法编写三个参数, 分别是:

  • identity

  • accumulator

  • combiner

关于这三个参数的意义, 如果不太理解, 可以看看这个答案: https://segmentfault.com/q/1010000004944450

我们可以设计一个类, 为这三个参数设计三个方法, 再提供一个方法用于获取目标类型(如果这个类就是目标类型的话, 可以不提供这个方法)

实现Collector接口

如果不想显式的使用reduce方法, 我们只需要提供一个类, 实现Collector接口.

该接口需要三个泛型参数, 依次是:

  • 待收集元素的类型

  • 累加器的类型

  • 最终结果的类型

需要实现的方法有:

  • supplier: 生成初始容器

  • accumulator: 累加计算方法

  • combiner: 在并发流中合并容器

  • finisher: 将容器转换成最终值

  • characteristics: 获取特征集合

多数情况下, 我们的容器器和我们的目标类型并不一致, 这时, 需要实现finisher方法将容器转化为目标类型, 比如调用容器的toString方法.

有时我们的目标类型就是我们的容器, finisher方法就不需要对容器做任何操作, 而是通过设置characteristicsIDENTITY_FINISH, 使用框架提供的优化得到结果.

详细讲解可以参见http://irusist.github.io/2016/01/04/Java-8%E4%B9%8BCollector/

Map新增方法

Java 8Map新增了很多方法, 可以通过搜索引擎轻松找到相关文章. 这里举几个书中提到的相关方法.

  • V computeIfAbsent(K key, Function mappingFunction)

  • V computeIfPresent(K key, BiFunction remappingFunction)

  • V compute(K key, BiFunction remappingFunction)

这三个方法类似, 都是根据key来处理, 只是Lambda表达式的执行条件不同, 从函数名就可以看出来. 不过要注意Lambda表达式的参数, 第一个方法的Lambda只需要一个参数key, 后面两个方法的Lambda需要两个参数keyvalue, 而compute方法的Lambda中的value参数可能为null.

  • V merge(K key, V value, BiFunction remappingFunction)

此方法用于合并value, 新value在第二个参数给出. Lambda表达式规定合并方法, 其两个参数依次是oldValuenewValue, oldValue是原Mapvalue, 可能为空; newValuemerge方法的第二个参数.

  • void forEach(BiConsumer action)

通过forEach方法, 不再需要使用外部迭代来遍历Map.

你可能感兴趣的:(lambda,java)