从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

    • 我的 Java 学习路线
    • 安装 Java
    • Java 数据类型

    • Java 多版本配置
    • 面向对象

    • Java 核心类

    • IO

    • Java 与时间

    • 异常处理

    • 哈希和加密算法

    • Java8 新特性

      • 函数式编程与 Lambda
      • Stream 介绍
      • 创建 Stream
      • 操作 Stream
        • map
        • filter
        • reduce
      • Optional
    • 网络编程

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java
  • JavaSE
  • Java8 新特性
2023-03-13
目录

操作 Stream

# 操作 Stream

简单说下 Stream 的操作

# map

Stream.map() 是 Stream 最常用的一个转换方法,它把一个 Stream 转换为另一个 Stream。

所谓 map 操作,就是把一种操作运算,映射到一个序列的每一个元素上。例如,对 x 计算它的平方,可以使用函数 f(x) = x * x。我们把这个函数映射到一个序列 1,2,3,4,5 上,就得到了另一个序列 1,4,9,16,25:

            f(x) = x * x

                  │
                  │
  ┌───┬───┬───┬───┼───┬───┬───┬───┐
  │   │   │   │   │   │   │   │   │
  ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓

[ 1   2   3   4   5   6   7   8   9 ]

  │   │   │   │   │   │   │   │   │
  │   │   │   │   │   │   │   │   │
  ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓

[ 1   4   9  16  25  36  49  64  81 ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

可见,map 操作,把一个 Stream 的每个元素一一对应到应用了目标函数的结果上。

Stream<Integer> s = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s2 = s.map(n -> n * n);
1
2

如果我们查看 Stream 的源码,会发现 map() 方法接收的对象是 Function 接口对象,它定义了一个 apply() 方法,负责把一个 T 类型转换成 R 类型:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);
1

其中,Function 的定义是:

@FunctionalInterface
public interface Function<T, R> {
    // 将T类型转换为R:
    R apply(T t);
}
1
2
3
4
5

利用 map(),不但能完成数学计算,对于字符串操作,以及任何 Java 对象都是非常有用的。例如:

Arrays.asList(" Peter ", "JXL", "heLLo")
.stream()
.map(String::trim)
.map(String::toLowerCase)
.forEach(System.out::println);
1
2
3
4
5

通过若干步 map 转换,可以写出逻辑简单、清晰的代码。

# filter

Stream.filter() 是 Stream 的另一个常用转换方法。

所谓 filter() 操作,就是对一个 Stream 的所有元素一一进行测试,不满足条件的就被“滤掉”了,剩下的满足条件的元素就构成了一个新的 Stream。

例如,我们对 1,2,3,4,5 这个 Stream 调用 filter(),传入的测试函数 f(x) = x % 2 != 0 用来判断元素是否是奇数,这样就过滤掉偶数,只剩下奇数,因此我们得到了另一个序列 1,3,5:

            f(x) = x % 2 != 0

                  │
                  │
  ┌───┬───┬───┬───┼───┬───┬───┬───┐
  │   │   │   │   │   │   │   │   │
  ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓

[ 1   2   3   4   5   6   7   8   9 ]

  │   X   │   X   │   X   │   X   │
  │       │       │       │       │
  ↓       ↓       ↓       ↓       ↓

[ 1       3       5       7       9 ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

用 IntStream 写出上述逻辑,代码如下:

IntStream.of(1,2,3,4,5,6,7,8,9)
.filter(n -> n % 2 !=0)
.forEach(System.out::println);
1
2
3

从结果可知,经过 filter() 后生成的 Stream 元素可能变少。

filter() 方法接收的对象是 Predicate 接口对象,它定义了一个 test() 方法,负责判断元素是否符合条件:

@FunctionalInterface
public interface Predicate<T> {
    // 判断元素t是否符合条件:
    boolean test(T t);
}
1
2
3
4
5

filter() 除了常用于数值外,也可应用于任何 Java 对象。例如,从一组给定的 LocalDate 中过滤掉工作日,以便得到休息日:

import java.time.*;
import java.util.function.*;
import java.util.stream.*;
public class Main {
    public static void main(String[] args) {
        Stream.generate(new LocalDateSupplier())
                .limit(31)
                .filter(ldt -> ldt.getDayOfWeek() == DayOfWeek.SATURDAY || ldt.getDayOfWeek() == DayOfWeek.SUNDAY)
                .forEach(System.out::println);
    }
}

class LocalDateSupplier implements Supplier<LocalDate> {
    LocalDate start = LocalDate.of(2020, 1, 1);
    int n = -1;
    public LocalDate get() {
        n++;
        return start.plusDays(n);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# reduce

map() 和 filter() 都是 Stream 的转换方法,而 Stream.reduce() 则是 Stream 的一个聚合方法,它可以把一个 Stream 的所有元素按照聚合函数聚合成一个结果。

我们来看一个简单的聚合方法:

import java.util.stream.*;
public class Main {
    public static void main(String[] args) {
        int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
                    .reduce(0, (acc, n) -> acc + n);
        System.out.println(sum); // 45
    }
}

1
2
3
4
5
6
7
8
9

reduce() 方法传入的对象是 BinaryOperator 接口,它定义了一个 apply() 方法,负责把上次累加的结果和本次的元素 进行运算,并返回累加的结果:

@FunctionalInterface
public interface BinaryOperator<T> {
    // Bi操作:两个输入,一个输出
    T apply(T t, T u);
}
1
2
3
4
5

上述代码看上去不好理解,但我们用 for 循环改写一下,就容易理解了:

Stream<Integer> stream = ...
int sum = 0;
for (n : stream) {
    sum = (sum, n) -> sum + n;
}
1
2
3
4
5

可见,reduce() 操作首先初始化结果为指定值(这里是 0),紧接着,reduce() 对每个元素依次调用 (acc, n) -> acc + n,其中,acc 是上次计算的结果:

// 计算过程:
acc = 0 // 初始化为指定值
acc = acc + n = 0 + 1 = 1 // n = 1
acc = acc + n = 1 + 2 = 3 // n = 2
acc = acc + n = 3 + 3 = 6 // n = 3
acc = acc + n = 6 + 4 = 10 // n = 4
acc = acc + n = 10 + 5 = 15 // n = 5
acc = acc + n = 15 + 6 = 21 // n = 6
acc = acc + n = 21 + 7 = 28 // n = 7
acc = acc + n = 28 + 8 = 36 // n = 8
acc = acc + n = 36 + 9 = 45 // n = 9
1
2
3
4
5
6
7
8
9
10
11

因此,实际上这个 reduce() 操作是一个求和。

如果去掉初始值,我们会得到一个 Optional<Integer>:

Optional<Integer> opt = stream.reduce((acc, n) -> acc + n);
if (opt.isPresent()) {
    System.out.println(opt.get());
}
1
2
3
4

这是因为 Stream 的元素有可能是 0 个,这样就没法调用 reduce() 的聚合函数了,因此返回 Optional 对象,需要进一步判断结果是否存在。

利用 reduce(),我们可以把求和改成求积,代码也十分简单:

import java.util.stream.*;
public class Main {
    public static void main(String[] args) {
        int s = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).reduce(1, (acc, n) -> acc * n);
        System.out.println(s); // 362880
    }
}

1
2
3
4
5
6
7
8

注意:计算求积时,初始值必须设置为 1。

除了可以对数值进行累积计算外,灵活运用 reduce() 也可以对 Java 对象进行操作。下面的代码演示了如何将配置文件的每一行配置通过 map() 和 reduce() 操作聚合成一个 Map<String, String>:

import java.util.*;
public class Main {
    public static void main(String[] args) {
        // 按行读取配置文件:
        List<String> props = List.of("profile=native", "debug=true", "logging=warn", "interval=500");
        Map<String, String> map = props.stream()
                // 把k=v转换为Map[k]=v:
                .map(kv -> {
                    String[] ss = kv.split("\\=", 2);
                    return Map.of(ss[0], ss[1]);
                })
                // 把所有Map聚合到一个Map:
                .reduce(new HashMap<String, String>(), (m, kv) -> {
                    m.putAll(kv);
                    return m;
                });
        // 打印结果:
        map.forEach((k, v) -> {
            System.out.println(k + " = " + v);
        });
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

小结

reduce() 方法将一个 Stream 的每个元素依次作用于 BinaryOperator,并将结果合并。

reduce() 是聚合方法,聚合方法会立刻对 Stream 进行计算。

上次更新: 2025/6/3 09:31:54
创建 Stream
Optional

← 创建 Stream Optional→

最近更新
01
学点统计学:轻松识破一本正经的胡说八道
06-05
02
2025 年 5 月记
05-31
03
《贫穷的本质》很棒,但可能不适合你
05-27
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式