《Java 8 函数式编程》笔记4
高级集合类和收集器
方法引用
标准语法:Classname::methodName
比如想得到艺术家的名字:
1 | lambda:artist -> artist.getName() |
构造方法同样可以缩写:
1 | lambda:(name, nationality) -> new Artist(name, nationality) |
元素顺序
本身是有序集合,比如 List
,创建流时,流中的元素就有顺序:
1 | List<Integer> numbers = Arrays.asList(1, 2, 3, 4); |
本身是无序集合,比如 HashSet
,由此生成的流也是无序的:
1 | Set<Integer> numbers = new HashSet<>(Arrays.asList(4, 3, 2, 1)); |
可以使用 sorted()
,让流里的元素有序:
1 | Set<Integer> numbers = new HashSet<>(Arrays.asList(4, 3, 2, 1)); |
或者使用 unordered()
,变无序:
1 | Set<Integer> numbers = new HashSet<>(Arrays.asList(4, 3, 2, 1)); |
使用收集器
collect(Collectors.toList())
,在流中生成列表。
类似的还有 Map
、Set
等。
转换为其他集合
比如转换为 TreeSet
,而不是框架背后为你指定的一种类型的 Set
:
1 | List<Integer> numbers = Arrays.asList(4, 3, 2, 1); |
转换为值
maxBy
和 minBy
找出成员最多的乐队:
1 | public Optional<Artist> biggestGroup(Stream<Artist> artists) { |
找出一组专辑上单曲的平均数:
1 | public double averageNumberOfTracks(List<Album> albums) { |
数据分块
假设有一个艺术家组成的流,一部分是独唱歌手,另一部分是乐队。
如果你希望将其分成两部分,可以使用收集器 partitioningBy
,它接受一个流, 并将其分成两部分:
1 | public Map<Boolean, List<Artist>> soloAndBands(Stream<Artist> artists) { |
数据分组
与将数据分成 true
和 false
两块不同,数据分组是一种更自然的分割数据操作,可以使用任意值对数据分组。
比如,现在有一个专辑组成的流,可以按专辑当中的乐队主唱对专辑分组:
1 | public Map<Artist, List<Album>> albumsByArtist(Stream<Album> albums) { |
字符串
比如要得到 “[{A, B, C}]” 这样的字符串:
1 | public String getString() { |
Collectors.joining(分隔符, 前缀, 后缀)
组合收集器
如何计算一个艺术家的发行的专辑数量?
最简单的就是使用前面的方法:对专辑先按艺术家分组,然后计数:
1 | Map<Artist, List<Album>> albumsByArtist = albums.collect(groupingBy(Album::getMainMusician)); |
这段代码固然简单,但有点杂乱,命令式的代码,也无法自动适应并行化的操作。
使用 counting
重写:
1 | Map<Artist, Long> numberOfAlbums = albums.collect( |
groupingBy
先将元素分块,每块都与 getMainMusician
提供的键相关联,然后使用下游的另一个收集器收集每块中的元素,最后将结果映射为 Map
。
另一个例子:如何获得每个艺术家的每张专辑名,而不是每张专辑?
1 | Map<Artist, List<Album>> albumsByArtist = albums.collect(groupingBy(Album::getMainMusician)); |
groupingBy
将专辑按主唱分组,输出了 Map<Artist, List<Album>>
,它将每个艺术家和他的专辑列表关联起来。
但我们需要的是 Map<Artist, List<String>>
,将每个艺术家和他的专辑名列表关联起来。
mapping
可以像 map
一样将 groupingBy
的值做映射,生成我们想要的结果:
1 | albums.collect( |
Map 类的变化
用 Map
实现缓存,传统方法:先试着取值,如果值为空,创建一个新值并返回。
1 | public Artist getArtist(String name) { |
computeIfAbsent
方法会在值不存在时,使用 lambda
表达式计算新值:
1 | public Artist getArtistUsingComputeIfAbsent(String name) { |
你可能试过在 Map
上迭代,比如:
1 | Map<Artist, List<Album>> albumsByArtist = albums.collect(groupingBy(Album::getMainMusician)); |
虽然工作正常,但是看起来挺丑的。
使用 forEach
内部迭代 Map
里的值:
1 | Map<Artist, List<Album>> albumsByArtist = albums.collect(groupingBy(Album::getMainMusician)); |