前面用 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.6

Function.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.0

summarizingInt 一次收集数量、总和、最小值、最大值和平均值。这样按年代分组后,就能直接查看每组电影的时长统计。

最后修改:2018 年 06 月 17 日
如果觉得我的文章对你有用,请随意赞赏