前面用 Stream 筛选电影,最后通过 collect(Collectors.toList()) 得到一个列表。如果要按名称查电影,或者按年份分组,又该怎么写?
继续使用上一篇的 Movie 类和 Movie.samples() 数据。下面的片段分别放在 main 方法中运行,需要这些导入和变量:
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class CollectDemo {
public static void main(String[] args) {
List<Movie> movieList = Movie.samples();
// 放入下面要运行的示例
}
}收集成 List
取得所有电影名称:
List<String> names = movieList.stream()
.map(Movie::getName)
.collect(Collectors.toList());
System.out.println(names);输出:
[无极, 救火英雄, 喜剧之王, 大话西游之月光宝盒]collect 是终止操作,Collectors.toList() 告诉它用什么方式收集结果。如果只是想把名称拼成一段文字,可以用 joining:
String names = movieList.stream()
.map(Movie::getName)
.collect(Collectors.joining("、"));
System.out.println(names);输出为 无极、救火英雄、喜剧之王、大话西游之月光宝盒。
收集成 Map
以电影名称作为 key,Movie 对象作为 value:
Map<String, Movie> byName = movieList.stream()
.collect(Collectors.toMap(Movie::getName, Function.identity()));
System.out.println(byName.get("喜剧之王").getScore()); // 8.6Function.identity() 就是返回传入的对象,这里也可以写成 movie -> movie。
这份数据的名称没有重复。如果 key 重复,两参数的 toMap 会抛出 IllegalStateException,并不会自动保留后一条。
比如把同一部电影加入两次:
List<Movie> repeated = Arrays.asList(movieList.get(0), movieList.get(0));
Map<String, Movie> byName = repeated.stream()
.collect(Collectors.toMap(Movie::getName, Function.identity()));如果这里希望保留先遇到的值,可以传入合并函数:
List<Movie> repeated = Arrays.asList(movieList.get(0), movieList.get(0));
Map<String, Movie> byName = repeated.stream()
.collect(Collectors.toMap(
Movie::getName,
Function.identity(),
(first, second) -> first));
System.out.println(byName.size()); // 1按年份分组
一个 key 需要对应多部电影时,用 groupingBy 更直接:
Map<Integer, List<Movie>> byYear = movieList.stream()
.collect(Collectors.groupingBy(Movie::getYear));
System.out.println(byYear.get(1999).get(0).getName()); // 喜剧之王groupingBy(Movie::getYear) 会把年份相同的电影收集到同一个 List 中。
这四部电影的年份各不相同。换成年代分组,就能看到两部 90 年代电影在一起:
Map<Integer, List<String>> byDecade = movieList.stream()
.collect(Collectors.groupingBy(
movie -> movie.getYear() / 10 * 10,
TreeMap::new,
Collectors.mapping(Movie::getName, Collectors.toList())));
System.out.println(byDecade);输出:
{1990=[喜剧之王, 大话西游之月光宝盒], 2000=[无极], 2010=[救火英雄]}这里三个参数分别是分组依据、结果 Map 的创建方式,以及每组如何收集。mapping 先取电影名称,再放进 List;指定 TreeMap,是为了让结果按年代排列。默认的 groupingBy 不保证 Map 的遍历顺序。
分组以后做统计
如果只需要每个年代的电影数量,把每组的收集方式换成 counting():
Map<Integer, Long> counts = movieList.stream()
.collect(Collectors.groupingBy(
movie -> movie.getYear() / 10 * 10,
TreeMap::new,
Collectors.counting()));
System.out.println(counts);输出:
{1990=2, 2000=1, 2010=1}统计时长也类似:
Map<Integer, IntSummaryStatistics> durations = movieList.stream()
.collect(Collectors.groupingBy(
movie -> movie.getYear() / 10 * 10,
Collectors.summarizingInt(Movie::getDuration)));
IntSummaryStatistics nineties = durations.get(1990);
System.out.println(nineties.getCount()); // 2
System.out.println(nineties.getSum()); // 172
System.out.println(nineties.getAverage()); // 86.0summarizingInt 一次收集数量、总和、最小值、最大值和平均值。这样按年代分组后,就能直接查看每组电影的时长统计。