1、方法引用
标准语法为 Classname::methodName。需要注意的是,虽然这是一个方法,但不需要在后面加括号,因为这里并不调用该方法。我们只是提供了和 Lambda 表达式等价的一种结构,在需要时才会调用。凡是使用 Lambda 表达式的地方,就可以使用方法引用。
2、使用收集器
在常用流操作一章中简略的介绍了收集器的使用。通常情况下,创建集合时需要调用适当的构造函数指明集合的具体类型。但是调用 toList 或者 toSet 方法时,不需要指定具体的类型。Stream 类库在背后自动为你挑选出了合适的类型。
如果希望使用 TreeSet,而不是由框架在背后自动为你指定一种类型的Set。此时就可以使用 toCollection,它接受一个函数作为参数,来创建集合。stream.collect(toCollection(TreeSet::new));
还可以利用收集器让流生成一个值。maxBy 和 minBy 允许用户按某种特定的顺序生成一个值。
public Optional<Artist> biggestGroup(Stream<Artist> artists) {
Function<Artist,Long> getCount = artist -> artist.getMembers().count();
return artists.collect(maxBy(comparing(getCount))); // 求最大值
}
public double averageNumberOfTracks(List<Album> albums) {
return albums.stream()
.collect(averagingInt(album -> album.getTrackList().size())); // 求平均数
}
也可以使用 summingInt 及其重载方法求和
3、数据分块
我们有这样一个收集器 partitioningBy,它接受一个流,并将其分成两部分。它使用 Predicate 对象判断一个元素应该属于哪个部分,并根据布尔值返回一个 Map 到列表。因此,对于 true List 中的元素,Predicate 返回 true;对其他 List 中的元素,Predicate 返回 false。
将Artist分为乐队与独唱歌手两部分
public Map<Boolean, List<Artist>> bandsAndSolo(Stream<Artist> artists) {
return artists.collect(partitioningBy(artist -> artist.isSolo()));
}
4、数据分组(groupingBy)
调用流的 collect 方法,传入一个收集器。groupingBy 收集器接受一个分类函数,用来对数据分组
public Map<Artist, List<Album>> albumsByArtist(Stream<Album> albums) {
return albums.collect(groupingBy(album -> album.getMainMusician())); // 使用groupBy对专辑分组
}
5、字符串
假设我们想将参与制作一张专辑的所有艺术家的名字输出为一个格式化好的列表,以专辑 Let It Be 为例,期望的输出为:”[George Harrison, John Lennon, Paul McCartney, Ringo Starr, The Beatles]”。
String result = artists.stream()
.map(Artist::getName)
.collect(Collectors.joining(", ", "[", "]"));
6、组合收集器
这里实际上需要另外一个收集器,告诉 groupingBy 不用为每一个艺术家生成一个专辑列表,只需要对专辑计数就可以了。幸好,核心类库已经提供了一个这样的收集器:counting。
使用收集器计算每个艺术家的专辑数
public Map<Artist, Long> numberOfAlbums(Stream<Album> albums) {
return albums.collect(groupingBy(album -> album.getMainMusician(), counting()));
}
使用收集器求每个艺术家的专辑名
public Map<Artist, List<String>> nameOfAlbums(Stream<Album> albums) {
return albums.collect(groupingBy(Album::getMainMusician, mapping(Album::getName, toList())));
}
7、重构和定制收集器
有时候Collectors类的收集器不能满足需求,需要我们自定义收集器。
自定义收集器需要实现Collector
接口
T -> 流中元素的类型 A -> 中间的收集容器类型 R -> 返回的结果类型
需要重写五个重要方法
Supplier_<_A_> _supplier_()_;
返回的是中间收集的一个容器,类型为A
BiConsumer_<_A, T_> _accumulator_()_;
不断从流中遍历元素,然后不断的将T累加到A当中
BinaryOperator_<_A_> _combiner_()_;
针对并行流,将多个线程执行的结果合并到一起
Function_<_A, R_> _finisher_()_;
作用是将累计的结果A转换为结果R,有时A和R类型是一样的
Set_<_Characteristics_> _characteristics_()_;
表示收集器的特性,其特性值来自于Characteristics枚举类
自定义收集器示例代码:
public class MyCollector<T> implements Collector<T,Set<T>,Set<T>> {
/**
* 初始化累加器A -> Set<T>
* @return
*/
@Override
public Supplier<Set<T>> supplier() {
System.out.println("supplier invoked");
return HashSet::new;
}
/**
* 遍历流中元素,将其累加到累加器中
* @return
*/
@Override
public BiConsumer<Set<T>,T> accumulator() {
System.out.println("accumulator invoked");
return Set::add;
}
/**
* 并行流操作,将多个并行流合并成一个流
* @return
*/
@Override
public BinaryOperator<Set<T>> combiner() {
System.out.println("combiner invoked");
return (set1,set2) -> {
set1.addAll(set2);
return set1;
};
}
/**
* 当中间累加容器与结果容器类型一致时,则不会调用此方法
* @return
*/
@Override
public Function<Set<T>,Set<T>> finisher() {
System.out.println("finisher invoked");
return Function.identity(); // 输入啥,输出啥
}
@Override
public Set<Characteristics> characteristics() {
System.out.println("characteristics invoked");
return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH,Characteristics.UNORDERED));
}
}
测试代码:
public static void main(String[] args) {
List<String> strings = Arrays.asList("hello", "world", "111");
Set<String> collect = strings.stream().collect(new MyCollector<>());
System.out.println(collect);
执行结果:
supplier invoked
accumulator invoked
combiner invoked
characteristics invoked
characteristics invoked
[111, world, hello]
8、使用computeIfAbsent
如果map中某个值为空,则会调用readArtistFromDB方法
public Artist getArtist(String name) {
return artistCache.computeIfAbsent(name, this::readArtistFromDB);
}