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

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

方法引用

形如:git

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

这样的表达式, 能够简写为:github

Artist::getName
String::length

这种简写的语法被称为方法引用. 方法引用无需考虑参数, 由于一个方法引用能够在不一样的状况下解析为不一样的Lambda表达式, 这依赖于JVM的推断.segmentfault

方法引用的类型

方法引用能够分为四类:安全

  • 引用静态方法: ClassName::staticMethodName, 好比: String.valueOf数据结构

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

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

  • 引用构造方法: ClassName::new, 好比: String::new框架

元素顺序

当咱们对集合进行操做时, 有时但愿是按照必定的顺序来操做, 而有时又但愿是乱序的操做. 有两个方法能够帮助咱们进行顺序的操做.ide

乱序

BaseStream.unordered()方法能够打乱顺序, 科技将原本有序的集合变成无序的集合

排序

Stream.sorted方法有两个签名, 一个无参, 一个有参数Comparator<? super T> 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<Boolean, List>, 而后就能够对相应的数据进行处理了.

数据分组

groupingBy方法能够将流分红多个List, 而不单单是两个, 接收一个Lambda表达式做为参数, 其返回值做为key, 最后的结果也是一个Map, 形如Map<String, List>. 这一方法相似于SQL中的group by

生成字符串

若是要从流中获得字符串, 能够在获得Stream<String>以后使用Collectors.joining方法收集. 该方法接收3个String参数, 分别是分隔符 前缀 后缀

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

组合收集器

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

  • 例一

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

这个方法的目的是统计每一个歌手的做品数目. 若是不组合收集器, 咱们先用groupingBy获得一个Map<Artist, List<Album>>以后, 还要去遍历Map获得统计数目, 增长了代码量和性能开销.

上面的counting方法相似于count方法, 做用于List<Album>的流上.

  • 例二

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

这个方法的目的是获得每一个歌手的做品名称列表. 若是不组合收集器, 咱们将会先获得一个Map<Artist, List<Album>>. 然而, 咱们只想获得做品名称, 也就是一个List<String>, 组合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<? super K, ? extends V> mappingFunction)

  • V computeIfPresent(K key, BiFunction<? super K, ? super V, extends V> remappingFunction)

  • V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

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

  • V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)

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

  • void forEach(BiConsumer<? super K, ? super V> action)

经过forEach方法, 再也不须要使用外部迭代来遍历Map.

相关文章
相关标签/搜索