高阶函数
高阶函数是指接受另外一个函数作为参数,或返回一个函数的函数。高阶函数不难辨认:看函数签名就够了。如果函数的参数列表里包含函数接口,或该函数返回一个函数接口,那么该函数就是高阶函数。
基本类型
在 Java
中,有一些相伴的类型,比如 int
和 Integer
,前者是基本类型,后者是装箱类型。基本类型内建在语言和运行环境中,是基本的程序构建模块;而装箱类型属于普通的 Java
类,只不过是对基本类型的一种封装。
Java 的泛型是基于对泛型参数类型的擦除——换句话说,假设它是 Object
对象的实例,因此只有装箱类型才能作为泛型参数。这就解释了为什么在 Java
中想要一个包含整型值的列表 List<int>
,实际上得到的却是一个包含整型对象的列表 List<Integer>
。
将基本类型转换为装箱类型,称为装箱,反之则称为拆箱,两者都需要额外的计算开销。对于需要大量数值运算的算法来说,装箱和拆箱的计算开销,以及装箱类型占用的额外内存,会明显减缓程序的运行速度。
为了减小这些性能开销,Stream
类的某些方法对基本类型和装箱类型做了区分。在 Java 8 中,仅对整型、长整型和双浮点型做了特殊处理,因为它们在数值计算中用得最多,特殊处理后的系统性能提升效果最明显。
方法引用
Lambda 表达式有一个常见的用法:Lambda
表达式经常调用参数。比如想得到艺术家的姓名,Lambda
的表达式如下:
artist -> artist.getName()
Java 8 为其提供了一个简写语法,叫作方法引用,帮助程序员重用已有方法。用方法引用重写上面的 Lambda
表达式,代码如下:
Artist::getName
转换成其他集合
比如 toList
、toSet
和 toCollection
,还会有这样的情况,你希望使用一个特定的集合收集值,此时就可以使用 toCollection
,它接受一个函数作为参数,来创建集合。
TreeSet<String> collect = Stream.of("a", "b", "c")
.collect(Collectors.toCollection(TreeSet::new));
转换成值
还可以利用收集器让流生成一个值,例:找出成员最多的乐队。
public Optional<Artist> biggestGroup(Stream<Artist> artists) {
Function<Artist,Long> getCount = artist -> artist.getMembers().count();
return artists.collect(maxBy(comparing(getCount)));
}
minBy
就如它的方法名,是用来找出最小值的,还有些收集器 maxBy
(最大值)、averagingInt
(平均数)、groupingBy
(分组)等等... ...
数据分块
另外一个常用的流操作是将集合按条件分解成两个集合。可以使用两次过滤操作,分别过滤出想要的数据,但是这样操作起来有问题。首先,为了执行两次过滤操作,需要有两个流。其次,如果过滤操作复杂,每个流上都要执行这样的操作,代码也会变得冗余。
幸好我们有这样一个收集器 partitioningBy
,它接受一个流,并将其分成两部分它使用 Predicate
对象判断一个元素应该属于哪个部分,并根据布尔值返回一
个 Map 到列表。因此,对于 true List 中的元素,Predicate 返回 true;对其他 List 中的元素,Predicate 返回 false。例:有一个艺术家组成的流,你可能希望
将其分成两个部分,一部分是独唱歌手,另一部分是由多人组成的乐队。
public Map<Boolean, List<Artist>> bandsAndSolo(Stream<Artist> artists) {
return artists.collect(partitioningBy(artist -> artist.isSolo()));
}
-------------------------------------------------------------------
public Map<Boolean, List<Artist>> bandsAndSoloRef(Stream<Artist> artists) {
return artists.collect(partitioningBy(Artist::isSolo));
}
数据分组
数据分组是一种更自然的分割数据操作,可以使用任意值对数据分组。例:在有一个由专辑组成的流,可以按专辑当中的主唱对专辑分组。
public Map<Artist, List<Album>> albumsByArtist(Stream<Album> albums) {
return albums.collect(groupingBy(album -> album.getMainMusician()));
}
和其他例子一样,调用流的 collect
方法,传入一个收集器。groupingBy
收集器接受一个分类函数,用来对数据分组。
字符串
有一种情况数据是为了在最后生成一个字符串,例:将参与制作一张专辑的所有艺术家的名字输出为一个格式化好的列表,可以使用 map
操作提取出艺术家的姓名,然后使用 Collectors.joining
收集流中的值,该方法可以方便地从一个流得到一个字符串,允许用户提供分隔符(用以分隔元素)、前缀和后缀。
String result = artists.stream()
.map(Artist::getName)
.collect(Collectors.joining(", ", "[", "]"));
组合收集器
例:计算一个艺术家的专辑数量。
public Map<Artist, Long> numberOfAlbums(Stream<Album> albums) {
return albums.collect(groupingBy(album -> album.getMainMusician(),
counting()));
}
groupingBy
先将元素分成块,每块都与分类函数 getMainMusician
提供的键值相关联,然后使用下游的另一个收集器收集每块中的元素,最好将结果映射为一个 Map
。
例:求每个艺术家的专辑名。
public Map<Artist, List<String>> nameOfAlbums(Stream<Album> albums) {
return albums.collect(groupingBy(Album::getMainMusician,
mapping(Album::getName, toList())));
}
mapping
收集器和 map
方法一样,接受一个 Function
对象作为参数,mapping
允许在收集器的容器上执行类似 map
的操作。但是需要指明使用什么样的集合类存储结果,比如 toList
。
并行化流操作
并行化操作流只需改变一个方法调用。 如果已经有一个 Stream
对象, 调用它的 parallel
方法就能让其拥有并行操作的能力。如果想从一个集合类创建一个流,调用 parallelStream
就能立即获得一个拥有并行能力的流。
public int serialArraySum() {
return albums.stream()
.flatMap(Album::getTracks)
.mapToInt(Track::getLength)
.sum();
}
->->->->->->->->->->->->->->->->->->->->->->
public int parallelArraySum() {
return albums.parallelStream()
.flatMap(Album::getTracks)
.mapToInt(Track::getLength)
.sum();
}
并行化数组操作
Java 8 还引入了一些针对数组的并行操作,这些操作都在工具类 Arrays 中。
方法名 | 操作 |
---|---|
parallelPrefix | 任意给定一个函数,计算数组的和 |
parallelSetAll | 使用 Lambda 表达式更新数组元素 |
parallelSort | 并行化对数组元素排序 |
更新于2019年04月19日