从01开始 从01开始
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)

peterjxl

人生如逆旅,我亦是行人
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)
  • JavaSE

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

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

    • Java核心类

    • IO

    • Java与时间

    • 异常处理

    • 哈希和加密算法

    • Java8新特性

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

    • Java
  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java并发

  • Java源码

  • JVM

  • 韩顺平

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

操作Stream

# 12.操作Stream

简单说下Steam的操作

# 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​进行计算。

‍

在GitHub上编辑此页 (opens new window)
上次更新: 2023/4/17 11:03:59
创建Stream
Optional

← 创建Stream Optional→

Theme by Vdoing | Copyright © 2022-2023 粤ICP备2022067627号-1 粤公网安备 44011302003646号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式