上一篇介绍了 Lambda 的几种写法,这次用一个电影列表试试 Stream。列表中有电影名称、评分、时长、年份和演员,先从统计评分不低于 8.0 的电影数量开始。

准备数据

这里使用四部电影,评分作为示例数据:

名称评分时长(分钟)年份
无极5.01212005
救火英雄7.21152014
喜剧之王8.6851999
大话西游之月光宝盒8.9871995

演员这里只用姓名,先用 List<String> 保存。把下面的代码保存为 Movie.java:

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;

public class Movie {
    private final String name;
    private final BigDecimal score;
    private final int duration;
    private final int year;
    private final List<String> actorList;

    public Movie(String name, String score, int duration, int year,
                 String... actors) {
        this.name = name;
        this.score = new BigDecimal(score);
        this.duration = duration;
        this.year = year;
        this.actorList = Arrays.asList(actors);
    }

    public String getName() { return name; }
    public BigDecimal getScore() { return score; }
    public int getDuration() { return duration; }
    public int getYear() { return year; }
    public List<String> getActorList() { return actorList; }

    public static List<Movie> samples() {
        return Arrays.asList(
            new Movie("无极", "5.0", 121, 2005,
                "张东健", "张柏芝", "真田广之", "谢霆锋", "刘烨"),
            new Movie("救火英雄", "7.2", 115, 2014,
                "谢霆锋", "余文乐", "任达华", "胡军"),
            new Movie("喜剧之王", "8.6", 85, 1999,
                "周星驰", "张柏芝", "莫文蔚"),
            new Movie("大话西游之月光宝盒", "8.9", 87, 1995,
                "周星驰", "吴孟达", "罗家英", "蓝洁瑛", "莫文蔚")
        );
    }
}

再创建 StreamDemo.java,后面的代码片段分别放进 main 方法运行:

import java.math.BigDecimal;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamDemo {
    public static void main(String[] args) {
        List<Movie> movieList = Movie.samples();
        BigDecimal score = new BigDecimal("8.0");
        // 在这里放入下面要运行的示例
    }
}

从循环改成 Stream

用 for 循环统计:

int count = 0;
for (Movie movie : movieList) {
    if (movie.getScore().compareTo(score) >= 0) {
        count++;
    }
}
System.out.println(count); // 2

换成 Stream:

long count = movieList.stream()
        .filter(movie -> movie.getScore().compareTo(score) >= 0)
        .count();
System.out.println(count); // 2

stream() 创建流,filter 描述筛选条件,count() 统计通过筛选的元素数量。这里没有从原列表中删除电影,movieList 仍然有四个元素。

惰性执行与短路

Stream 的操作分为中间操作和终止操作。filter、map、sorted 这些中间操作可以继续接其他操作;count、collect、findFirst 等终止操作则触发计算。

先只写筛选,不调用终止操作:

Stream<Movie> movies = movieList.stream().filter(movie -> {
    System.out.println("score:" + movie.getScore());
    return movie.getScore().compareTo(score) >= 0;
});

这时不会打印评分。上面只是描述要做什么,还没有开始遍历。

接着执行:

System.out.println("count:" + movies.count());

在 Java 8 中运行,输出:

score:5.0
score:7.2
score:8.6
score:8.9
count:2

终止操作触发后,元素依次经过筛选,并不是把列表从后往前处理。这个流已经用过,再做一次统计需要重新调用 movieList.stream()。

如果只需要第一个符合条件的元素,可以增加 limit(1):

long count = movieList.stream()
        .filter(movie -> {
            System.out.println("score:" + movie.getScore());
            return movie.getScore().compareTo(score) >= 0;
        })
        .limit(1)
        .count();
System.out.println("count:" + count);

输出:

score:5.0
score:7.2
score:8.6
count:1

这里使用顺序流。找到第一部满足条件的电影后,limit(1) 已经取得所需元素,后面的电影就不用再检查了。

map 与方法引用

只想取得符合条件的电影名称,可以用 map:

List<String> names = movieList.stream()
        .filter(movie -> movie.getScore().compareTo(score) >= 0)
        .map(Movie::getName)
        .collect(Collectors.toList());
System.out.println(names);

输出:

[喜剧之王, 大话西游之月光宝盒]

Movie::getName 是方法引用,这里对应 movie -> movie.getName()。map 把流中的每个 Movie 转换成名称,collect(Collectors.toList()) 再把结果收集成 List。

flatMap 与去重

每部电影都有一个演员列表。用 map(Movie::getActorList) 得到的是 Stream<List<String>>,每个元素还是一个列表。

如果想把所有演员放到一个流中,可以使用 flatMap:

List<String> actors = movieList.stream()
        .flatMap(movie -> movie.getActorList().stream())
        .distinct()
        .collect(Collectors.toList());
System.out.println(actors);

输出:

[张东健, 张柏芝, 真田广之, 谢霆锋, 刘烨, 余文乐, 任达华, 胡军, 周星驰, 莫文蔚, 吴孟达, 罗家英, 蓝洁瑛]

flatMap 把每部电影的演员流合并起来,distinct 按 equals 去重。这里比较的是姓名字符串,所以多部电影中出现的同一个姓名只保留一次。

排序

按评分从高到低排列,取前两部电影的名称:

List<String> names = movieList.stream()
        .sorted(Comparator.comparing(Movie::getScore).reversed())
        .limit(2)
        .map(Movie::getName)
        .collect(Collectors.toList());
System.out.println(names);

输出:

[大话西游之月光宝盒, 喜剧之王]

comparing 按评分比较,reversed() 改成降序。这里排序要先看完输入数据,后面虽然有 limit(2),也不代表只读取前两部电影。

Collection 怎么增加 stream 方法

Java 8 在 Collection 中增加了 stream(),但已有的集合实现类并不需要全部重新实现一遍。因为它是一个默认方法,接口已经提供了方法体:

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

这里第二个参数 false 表示顺序流。默认方法让接口能够提供实现,具体类可以直接继承,也可以按需要重写。

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