程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • Java8新特性

    • Java8新特性简介
    • Lambda 表达式
    • 函数式接口
    • 方法的引用
    • 构造器和数组的引用
    • Stream 流式编程
      • 1. 认识流式编程
        • 1.1 流式编程的概念和作用
        • 1.2 流式编程如何提高代码可读性和简洁性
      • 2. 流的基础示例
        • 2.1 环境搭建
        • 2.2 使用 Java 8 之前的做法
        • 2.3 使用 Stream 流式编程
        • 2.4 传统方式与流式编程的对比
      • 3. 创建 Stream
        • 3.1 通过集合创建 Stream
        • 3.2 通过数组创建 Stream
        • 3.3 使用 Stream.of() 创建 Stream
        • 3.4 创建无限流
        • 3.5 通过 Stream.empty() 创建空流
        • 3.6 通过 BufferedReader.lines() 创建流
      • 4. 流与函数式接口的结合
      • 5. Stream 中间操作
        • 1. filter (筛选数据)
        • 2. map (转换数据)
        • 3. flatMap (展平数据)
        • 4. limit (限制元素数量)
        • 5. skip (跳过元素)
        • 6. distinct (去重)
        • 7. sorted (排序)
      • 6. Stream 终止操作
        • 1. collect (数据收集)
        • 2. reduce (聚合计算)
        • 3. forEach (遍历操作)
        • 4. count (统计数据)
        • 5. anyMatch, allMatch, noneMatch (判断匹配)
        • 6. findFirst, findAny (查找元素)
    • 并行流与串行流
    • Optional 类的使用
    • 对反射的支持增强
    • 接口中的默认方法与静态方法
    • 新时间日期API
    • Try-with-Resources 升级
    • 重复注解与类型注解
  • Java9新特性

  • Java10新特性

  • Java11新特性

  • Java12新特性

  • Java13新特性

  • Java14新特性

  • Java15新特性

  • Java新特性
  • Java8新特性
scholar
2024-08-24
目录

Stream 流式编程

# Stream 流式编程

  • 1. 认识流式编程
    • 1.1 流式编程的概念和作用
    • 1.2 流式编程如何提高代码可读性和简洁性
  • 2. 流的基础示例
    • 2.1 环境搭建
    • 2.2 使用 Java 8 之前的做法
    • 2.3 使用 Stream 流式编程
    • 2.4 传统方式与流式编程的对比
  • 3. 创建 Stream
    • 3.1 通过集合创建 Stream
    • 3.2 通过数组创建 Stream
    • 3.3 使用 Stream.of() 创建 Stream
    • 3.4 创建无限流
    • 3.5 通过 Stream.empty() 创建空流
    • 3.6 通过 BufferedReader.lines() 创建流
  • 4. 流与函数式接口的结合
  • 5. Stream 中间操作
    • 1. filter (筛选数据)
    • 2. map (转换数据)
    • 3. flatMap (展平数据)
    • 4. limit (限制元素数量)
    • 5. skip (跳过元素)
    • 6. distinct (去重)
    • 7. sorted (排序)
  • 6. Stream 终止操作
    • 1. collect (数据收集)
    • 2. reduce (聚合计算)
    • 3. forEach (遍历操作)
    • 4. count (统计数据)
    • 5. anyMatch, allMatch, noneMatch (判断匹配)
    • 6. findFirst, findAny (查找元素)

# 1. 认识流式编程

# 1.1 流式编程的概念和作用

Java 流(Stream)是用于处理数据的一个功能强大的工具,它是基于函数式编程思想的一种实现。流可以看作是数据元素的序列,能够以声明式的方式对数据进行处理。

Stream 可以做什么?

  1. 筛选数据(filter()) → 只保留符合条件的数据
  2. 转换数据(map()) → 把一种数据转换成另一种数据
  3. 展平数据(flatMap()) → 把多个列表合并成一个列表
  4. 排序(sorted()) → 按照某种规则排列数据
  5. 去重(distinct()) → 去掉重复的数据
  6. 限制数据(limit()) → 只取前 n 个数据
  7. 跳过数据(skip()) → 忽略前 n 个数据
  8. 聚合计算(reduce()) → 计算总和、最大值、最小值等
  9. 数据收集(collect()) → 把流变成 List、Set、Map
  10. 查找元素(findFirst()、findAny()) → 找到某个数据
  11. 判断匹配(anyMatch()、allMatch()、noneMatch()) → 判断数据是否符合某个条件
  12. 统计数据(count()) → 计算数据的数量
  13. 并行处理(parallelStream()) → 提高大数据量处理的速度

# 1.2 流式编程如何提高代码可读性和简洁性

  1. 声明式编程风格: 只需描述对数据的操作,避免了繁琐的迭代和控制流,代码更加直观。
  2. 链式调用: 流式操作支持方法链式调用,可以将多个操作串联在一起,形成“流水线”式的处理流程。
  3. 操作的组合: 流提供丰富的操作方法(如过滤、映射、聚合等),这些操作可以灵活组合使用,形成复杂的数据处理流程。
  4. 减少中间状态: 不需要像传统方式那样引入中间变量或临时集合来保存中间结果,代码更加简洁。
  5. 减少循环和条件判断: 流提供了便捷的过滤、映射和聚合操作,避免繁琐的循环和条件判断。

# 2. 流的基础示例

# 2.1 环境搭建

我们首先创建一个演员类:

package com.trs.stream;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author : scholar
 * @version 1.0
 * @date 2023-10-24 9:42
 * @description : 示例
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Actor {
    /* 演员类包含演员ID、姓名、年龄和作品 */
    private Integer id;
    private String name;
    private Integer age;
    private String works;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

基于这个类,我们初始化一个演员集合:

package com.trs.stream;

import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;

/**
 * @author : scholar
 * @version 1.0
 * @date 2023-10-24 9:42
 * @description : 示例
 */
public class StreamTest01 {
    // 初始化演员列表
    public static final List<Actor> actorList = Arrays.asList(
        new Actor(1001, "张三", 30, "演员"),
        new Actor(1002, "李四", 70, "演员"),
        new Actor(1003, "王五", 40, "演员"),
        new Actor(1004, "赵六", 53, "演员"),
        new Actor(1005, "莉莉", 35, "演员"),
        new Actor(1006, "杨晓雪", 12, "演员"),
        new Actor(1007, "李师傅", 55, "演员"),
        new Actor(1008, "王胖子", 40, "演员"),
        new Actor(1009, "齐肩发", 45, "演员")
    );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 2.2 使用 Java 8 之前的做法

import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * @description: 使用传统方式筛选、排序并输出演员信息
 */
public class StreamTest01 {
    
    @Test
    public void test1() {
        List<Actor> ageList = new ArrayList<>();
        
        // 筛选出年龄小于40岁的演员
        for (Actor actor : actorList) {
            if (actor.getAge() < 40) {
                ageList.add(actor);
            }
        }

        // 按演员ID升序排序
        Collections.sort(ageList, new Comparator<Actor>() {
            @Override
            public int compare(Actor a1, Actor a2) {
                return Integer.compare(a1.getId(), a2.getId());
            }
        });

        // 将演员姓名存入列表
        List<String> actorNames = new ArrayList<>();
        for (Actor actor : ageList) {
            actorNames.add(actor.getName());
        }

        // 输出结果
        for (Actor actor : ageList) {
            System.out.println(actor);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

输出结果:

Actor{id=1001, name='张三', age=30, works='演员'}
Actor{id=1005, name='莉莉', age=35, works='演员'}
Actor{id=1006, name='杨晓雪', age=12, works='演员'}
1
2
3

这种传统的方式实现功能较为繁琐,需要手动管理集合、迭代和排序等逻辑,代码复杂且容易出错。下一步,我们可以通过流式编程大幅简化这段代码。

# 2.3 使用 Stream 流式编程

使用流式编程的方式,我们可以通过链式调用的方式完成数据的过滤、排序和处理,代码更加简洁且直观。下面是使用流式编程实现上述功能的代码:

import org.junit.jupiter.api.Test;
import java.util.List;
import static java.util.stream.Collectors.toList;
import static java.util.Comparator.comparing;

/**
 * @description: 使用流式编程筛选、排序并输出演员信息
 */
public class StreamTest01 {

    @Test
    public void test2() {
        actorList.stream()
                // 过滤演员年龄小于40岁的
                .filter(actor -> actor.getAge() < 40)
                // 按演员ID进行升序排序
                .sorted(comparing(Actor::getId))
                // 提取演员的姓名
                .map(Actor::getName)
                // 将结果收集为 List
                .collect(toList())
                // 输出每个演员的姓名
                .forEach(System.out::println);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

代码解析:

  1. filter: 筛选出年龄小于 40 岁的演员。
  2. sorted: 使用 Actor 的 ID 进行升序排序。
  3. map: 将每个演员对象转换为其姓名。
  4. collect: 将结果收集为一个 List。
  5. forEach: 逐一输出每个演员的姓名。

输出结果:

张三
莉莉
杨晓雪
1
2
3

# 2.4 传统方式与流式编程的对比

特性 传统方式 流式编程
代码可读性 代码较为繁琐,逻辑分散 代码简洁,操作直观
中间状态管理 需要手动管理中间集合和变量 通过流的操作链自动处理中间状态
函数式编程支持 基于循环、条件判断 支持 lambda 表达式和函数式编程
并行处理能力 手动实现并行处理 可以直接使用 parallelStream 进行并行处理
灵活性和组合性 扩展性差,组合操作复杂 提供了丰富的组合操作,如 map、filter 等

# 3. 创建 Stream

创建 Stream 是一切流式操作的起点。Java 8 提供了多种方式来创建流,使我们能够从不同的数据源开始流式处理。下面详细介绍几种常见的创建方法。

# 3.1 通过集合创建 Stream

集合是最常见的数据源,Java 8 在 Collection 接口中添加了 stream() 和 parallelStream() 方法,使得所有集合类都能轻松创建流。

方法介绍:

  • 方法: stream() 和 parallelStream()
  • 语法: default Stream<E> stream() 或 default Stream<E> parallelStream()
  • 参数: 无
  • 返回值: 返回一个顺序流或并行流。
  • 作用: 从集合中创建 Stream,用于顺序或并行处理集合元素。

代码示例:

import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

public class StreamCreationFromCollectionExample {
    public static void main(String[] args) {
        // 创建一个ArrayList集合
        List<String> arrayList = new ArrayList<>();
        arrayList.add("苹果");
        arrayList.add("香蕉");
        arrayList.add("橙子");
        
        // 从ArrayList创建顺序流
        Stream<String> streamFromArrayList = arrayList.stream();
        System.out.println("从ArrayList创建的顺序流:");
        streamFromArrayList.forEach(System.out::println); // 顺序输出:苹果、香蕉、橙子
        
        // 创建一个LinkedList集合
        List<String> linkedList = new LinkedList<>(Arrays.asList("猕猴桃", "葡萄", "西瓜"));
        
        // 从LinkedList创建并行流
        Stream<String> parallelStreamFromLinkedList = linkedList.parallelStream();
        System.out.println("\n从LinkedList创建的并行流:");
        // 注意:并行流的输出顺序可能不确定
        parallelStreamFromLinkedList.forEach(System.out::println);
        
        // 创建一个HashSet集合
        Set<Integer> numberSet = new HashSet<>();
        numberSet.add(1);
        numberSet.add(2);
        numberSet.add(3);
        
        // 从HashSet创建顺序流
        Stream<Integer> streamFromSet = numberSet.stream();
        System.out.println("\n从HashSet创建的顺序流:");
        streamFromSet.forEach(System.out::println); // 输出顺序可能不确定,因为HashSet不保证顺序
        
        // 演示顺序流和并行流的区别
        System.out.println("\n顺序流处理示例(添加线程信息):");
        arrayList.stream()
                .map(fruit -> {
                    System.out.println(Thread.currentThread().getName() + " 处理: " + fruit);
                    return fruit.toUpperCase();
                })
                .forEach(System.out::println);
        
        System.out.println("\n并行流处理示例(添加线程信息):");
        arrayList.parallelStream()
                .map(fruit -> {
                    System.out.println(Thread.currentThread().getName() + " 处理: " + fruit);
                    return fruit.toUpperCase();
                })
                .forEach(System.out::println);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

详细解析:

  • 顺序流 (stream()):

    • 顺序流按照集合中元素的顺序依次处理每个元素。
    • 所有操作都在同一个线程中执行。
    • 适用于数据量较小或操作简单的场景。
    • 保证处理顺序与集合中元素的顺序一致。
  • 并行流 (parallelStream()):

    • 并行流利用多线程并行处理集合中的元素。
    • 可以充分利用多核处理器的优势,提高处理速度。
    • 适用于数据量大或操作复杂的场景。
    • 不保证处理顺序与集合中元素的顺序一致。
    • 需要注意线程安全问题,避免使用有状态的操作。
  • 注意事项:

    • 流只能使用一次,使用后就会关闭,不能重复使用。
    • 并行流在处理大数据集时可能会提高性能,但也会增加线程开销。
    • 对于小数据集,顺序流通常比并行流更高效。
    • 并行流适合计算密集型操作,不适合IO密集型操作。

# 3.2 通过数组创建 Stream

Java 8 提供了 Arrays.stream() 方法,可以将数组转换为流,支持对象数组和基本类型数组。

方法介绍:

  • 方法: Arrays.stream()
  • 语法:
    • 对象数组:static <T> Stream<T> stream(T[] array)
    • 基本类型数组:
      • public static IntStream stream(int[] array)
      • public static LongStream stream(long[] array)
      • public static DoubleStream stream(double[] array)
  • 参数: 需要传入一个数组,可以是对象数组或基本类型数组。
  • 返回值: 返回一个流,流的类型与数组元素类型一致。
  • 作用: 将数组转换为 Stream,以便对数组元素进行流式处理。

代码示例:

import java.util.Arrays;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;

public class StreamCreationFromArrayExample {
    public static void main(String[] args) {
        // 创建一个整数数组
        int[] numbers = {1, 2, 3, 4, 5};

        // 将整数数组转换为IntStream
        IntStream intStream = Arrays.stream(numbers);
        System.out.println("整数数组流:");
        intStream.forEach(num -> System.out.print(num + " ")); // 输出:1 2 3 4 5
        
        // 创建一个长整型数组
        long[] longNumbers = {100L, 200L, 300L};
        
        // 将长整型数组转换为LongStream
        LongStream longStream = Arrays.stream(longNumbers);
        System.out.println("\n\n长整型数组流:");
        longStream.forEach(num -> System.out.print(num + " ")); // 输出:100 200 300
        
        // 创建一个双精度浮点数数组
        double[] doubleNumbers = {1.1, 2.2, 3.3};
        
        // 将双精度浮点数数组转换为DoubleStream
        DoubleStream doubleStream = Arrays.stream(doubleNumbers);
        System.out.println("\n\n双精度浮点数数组流:");
        doubleStream.forEach(num -> System.out.print(num + " ")); // 输出:1.1 2.2 3.3

        // 创建一个字符串数组
        String[] fruits = {"苹果", "香蕉", "橙子"};
        
        // 将字符串数组转换为Stream<String>
        Stream<String> fruitStream = Arrays.stream(fruits);
        System.out.println("\n\n字符串数组流:");
        fruitStream.forEach(System.out::println); // 输出:苹果、香蕉、橙子
        
        // 创建一个对象数组
        Person[] people = {
            new Person("张三", 25),
            new Person("李四", 30),
            new Person("王五", 35)
        };
        
        // 将对象数组转换为Stream<Person>
        Stream<Person> personStream = Arrays.stream(people);
        System.out.println("\n对象数组流:");
        personStream.forEach(person -> System.out.println(person.getName() + ", " + person.getAge()));
        
        // 指定数组的部分范围创建流
        IntStream partialStream = Arrays.stream(numbers, 1, 4); // 包含索引1到3的元素
        System.out.println("\n部分数组流(索引1到3):");
        partialStream.forEach(num -> System.out.print(num + " ")); // 输出:2 3 4
    }
    
    // 定义一个简单的Person类
    static class Person {
        private String name;
        private int age;
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        public String getName() {
            return name;
        }
        
        public int getAge() {
            return age;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

详细解析:

  • 基本类型流:

    • 对于基本类型数组,Arrays.stream() 返回相应的基本类型流(IntStream、LongStream、DoubleStream)。
    • 基本类型流提供了特定的方法,如 sum()、average() 等,可以直接对数值进行计算。
    • 使用基本类型流可以避免装箱和拆箱操作,提高性能。
  • 对象数组流:

    • 对于对象数组,Arrays.stream() 返回 Stream<T>,其中 T 是数组元素的类型。
    • 可以对任何类型的对象数组创建流,包括自定义类型。
  • 部分范围流:

    • Arrays.stream() 方法的重载版本允许指定数组的起始索引和结束索引。
    • 起始索引是包含的,结束索引是不包含的(左闭右开)。
  • 注意事项:

    • 数组创建的流是有限的,元素数量等于数组长度或指定范围内的元素数量。
    • 流创建后,对原数组的修改不会影响已创建的流。
    • 基本类型流比对象流更高效,因为它们避免了装箱和拆箱操作。

# 3.3 使用 Stream.of() 创建 Stream

Stream.of() 方法提供了一种简便的方式来创建流,可以直接从一系列值创建流,而不需要先创建集合或数组。

方法介绍:

  • 方法: Stream.of()
  • 语法: public static <T> Stream<T> of(T... values)
  • 参数: 接收任意数量的参数,可以是多个对象、字符串等。
  • 返回值: 返回一个由这些参数组成的流。
  • 作用: 直接通过指定的值创建 Stream,适用于快速创建流的场景。

代码示例:

import java.util.stream.Stream;

public class StreamOfExample {
    public static void main(String[] args) {
        // 使用Stream.of()创建包含字符串的流
        Stream<String> fruitStream = Stream.of("苹果", "香蕉", "橙子");
        System.out.println("字符串流:");
        fruitStream.forEach(System.out::println); // 输出:苹果、香蕉、橙子
        
        // 使用Stream.of()创建包含整数的流
        Stream<Integer> numberStream = Stream.of(1, 2, 3, 4, 5);
        System.out.println("\n整数流:");
        numberStream.forEach(num -> System.out.print(num + " ")); // 输出:1 2 3 4 5
        
        // 使用Stream.of()创建包含混合类型的流(不推荐,类型应该一致)
        Stream<Object> mixedStream = Stream.of("文本", 100, 3.14, true);
        System.out.println("\n\n混合类型流(不推荐):");
        mixedStream.forEach(item -> System.out.print(item + " ")); // 输出:文本 100 3.14 true
        
        // 使用Stream.of()创建包含单个元素的流
        Stream<String> singleElementStream = Stream.of("单个元素");
        System.out.println("\n\n单个元素流:");
        singleElementStream.forEach(System.out::println); // 输出:单个元素
        
        // 使用Stream.of()创建包含数组的流
        String[] fruits = {"苹果", "香蕉", "橙子"};
        Stream<String[]> arrayStream = Stream.of(fruits); // 注意:这会创建一个包含整个数组的流,而不是数组元素的流
        System.out.println("\n包含数组的流(注意区别):");
        arrayStream.forEach(array -> System.out.println("数组长度: " + array.length));
        
        // 正确的方式是使用Arrays.stream()或Stream.of()加展开运算符
        Stream<String> correctArrayStream = Stream.of(fruits[0], fruits[1], fruits[2]);
        // 或者使用:Stream<String> correctArrayStream = Arrays.stream(fruits);
        System.out.println("\n正确创建的数组元素流:");
        correctArrayStream.forEach(System.out::println);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

详细解析:

  • 基本用法:

    • Stream.of() 方法接受可变参数(varargs),可以传入任意数量的元素。
    • 创建的流包含所有传入的元素,按照传入的顺序排列。
    • 这是创建小型流的最简便方法,特别是当元素已知且数量有限时。
  • 类型一致性:

    • 虽然可以创建包含不同类型元素的流,但这通常不是好的实践。
    • 流操作通常假设所有元素都是相同类型,混合类型可能导致类型转换问题。
    • 建议保持流中元素类型的一致性。
  • 单个元素流:

    • 可以使用 Stream.of() 创建只包含一个元素的流。
    • 这在需要将单个值转换为流的场景中很有用。
  • 数组处理注意事项:

    • 当传入一个数组到 Stream.of() 时,创建的是包含整个数组的流,而不是数组元素的流。
    • 要创建包含数组元素的流,应该使用 Arrays.stream(array) 或者展开数组元素。
  • 与其他方法的比较:

    • Stream.of() 比先创建集合再调用 stream() 方法更简洁。
    • 对于已有的数组,Arrays.stream() 通常是更好的选择。
    • 对于即时创建的少量元素,Stream.of() 是最直接的方法。

# 3.4 创建无限流

无限流是一种特殊类型的流,它可以生成无限数量的元素。Java 8 提供了两种创建无限流的方法:Stream.iterate() 和 Stream.generate()。

方法介绍:

  • 方法: Stream.iterate() 和 Stream.generate()
  • 语法:
    • public static <T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
    • public static <T> Stream<T> generate(Supplier<T> s)
  • 参数:
    • iterate() 方法接收一个初始值(种子)seed 和一个迭代函数 f。
    • generate() 方法接收一个供应商函数 s,可以无限生成元素。
  • 返回值: 返回一个无限流,通常需要通过 limit() 限制流的大小。
  • 作用: 创建无限流,用于生成无限序列或随机数据。

代码示例:

import java.util.Random;
import java.util.UUID;
import java.util.stream.Stream;

public class InfiniteStreamExample {
    public static void main(String[] args) {
        // 使用iterate创建无限流 - 从0开始,每次加2,生成偶数序列
        Stream<Integer> evenNumbersStream = Stream.iterate(0, n -> n + 2);
        System.out.println("前10个偶数:");
        evenNumbersStream.limit(10) // 限制只取前10个元素
                        .forEach(n -> System.out.print(n + " ")); // 输出:0 2 4 6 8 10 12 14 16 18
        
        // 使用iterate创建斐波那契数列
        // 使用数组来存储状态,因为lambda表达式中使用的变量必须是final或effectively final
        Stream<Integer> fibonacciStream = Stream.iterate(new int[]{0, 1}, fib -> new int[]{fib[1], fib[0] + fib[1]})
                                              .map(fib -> fib[0]); // 提取数组的第一个元素
        System.out.println("\n\n斐波那契数列的前10个数:");
        fibonacciStream.limit(10)
                      .forEach(n -> System.out.print(n + " ")); // 输出:0 1 1 2 3 5 8 13 21 34
        
        // 使用generate创建无限流 - 生成随机数
        Random random = new Random();
        Stream<Integer> randomNumbersStream = Stream.generate(() -> random.nextInt(100)); // 生成0-99之间的随机数
        System.out.println("\n\n5个随机数:");
        randomNumbersStream.limit(5)
                          .forEach(n -> System.out.print(n + " ")); // 输出5个随机数
        
        // 使用generate创建无限流 - 生成UUID
        Stream<String> uuidStream = Stream.generate(() -> UUID.randomUUID().toString());
        System.out.println("\n\n3个UUID:");
        uuidStream.limit(3)
                 .forEach(System.out::println); // 输出3个UUID
        
        // 使用generate创建无限流 - 生成常量
        Stream<String> constantStream = Stream.generate(() -> "常量");
        System.out.println("\n生成常量流:");
        constantStream.limit(5)
                     .forEach(System.out::println); // 输出5次"常量"
        
        // Java 9及以上版本支持的iterate方法,带有谓词条件
        // Stream<Integer> numbersUntil100 = Stream.iterate(0, n -> n < 100, n -> n + 10);
        // System.out.println("\n从0开始,每次加10,直到小于100:");
        // numbersUntil100.forEach(n -> System.out.print(n + " ")); // 输出:0 10 20 30 40 50 60 70 80 90
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

详细解析:

  • Stream.iterate():

    • 从一个初始值(种子)开始,不断应用给定的函数生成下一个元素。
    • 每个新元素都是基于前一个元素计算得出的。
    • 适用于生成有规律的序列,如等差数列、等比数列等。
    • 可以用来模拟递归序列,如斐波那契数列。
    • Java 9 引入了带有谓词条件的 iterate 方法,可以在满足条件时停止迭代。
  • Stream.generate():

    • 通过反复调用提供的 Supplier 函数生成元素。
    • 每个元素的生成相互独立,不依赖于前一个元素。
    • 适用于生成随机数、UUID、常量等。
    • 由于元素之间没有关联,通常用于生成随机或独立的数据。
  • 无限流的特点:

    • 理论上可以生成无限数量的元素,但实际使用时通常需要限制。
    • 必须使用 limit()、takeWhile() 等方法限制元素数量,否则可能导致无限循环或内存溢出。
    • 无限流通常与短路操作(如 findFirst()、anyMatch() 等)一起使用,以确保终止。
  • 注意事项:

    • 使用无限流时必须小心,确保有终止条件。
    • 无限流不适合使用 collect() 等收集所有元素的终止操作,除非先限制元素数量。
    • 在并行流中使用无限流时要特别小心,可能导致不可预测的行为。

# 3.5 通过 Stream.empty() 创建空流

有时我们需要创建一个不包含任何元素的空流,Java 8 提供了 Stream.empty() 方法来实现这一点。

方法介绍:

  • 方法: Stream.empty()
  • 语法: public static<T> Stream<T> empty()
  • 参数: 无
  • 返回值: 返回一个空的 Stream。
  • 作用: 创建一个不包含任何元素的流,通常用于表示空结果或默认值。

代码示例:

import java.util.stream.Stream;

public class EmptyStreamExample {
    public static void main(String[] args) {
        // 创建一个空流
        Stream<String> emptyStream = Stream.empty();
        
        // 尝试对空流进行操作
        long count = emptyStream.count();
        System.out.println("空流中的元素数量: " + count); // 输出:0
        
        // 创建一个可能为空的流
        Stream<String> possiblyEmptyStream = getPossiblyEmptyStream(true);
        System.out.println("可能为空的流(条件为true):");
        possiblyEmptyStream.forEach(System.out::println); // 不输出任何内容
        
        // 创建一个非空流
        Stream<String> nonEmptyStream = getPossiblyEmptyStream(false);
        System.out.println("可能为空的流(条件为false):");
        nonEmptyStream.forEach(System.out::println); // 输出:数据1、数据2、数据3
    }
    
    // 根据条件返回空流或非空流
    private static Stream<String> getPossiblyEmptyStream(boolean isEmpty) {
        if (isEmpty) {
            return Stream.empty(); // 返回空流
        } else {
            return Stream.of("数据1", "数据2", "数据3"); // 返回非空流
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

详细解析:

  • 基本用法:

    • Stream.empty() 创建一个不包含任何元素的流。
    • 空流上的大多数操作都会返回空结果或默认值。
  • 应用场景:

    • 作为可能返回 null 的方法的替代,返回空流更符合函数式编程风格。
    • 在条件逻辑中,可以根据条件返回空流或非空流,避免空指针异常。
    • 作为默认值或回退选项,当没有数据可用时返回空流。
  • 注意事项:

    • 空流上的终止操作会立即返回,不会执行任何处理。
    • 对空流调用 count() 会返回 0,调用 findFirst() 会返回空的 Optional。
    • 空流比 null 更安全,因为它可以正常参与流操作,不会导致 NullPointerException。

# 3.6 通过 BufferedReader.lines() 创建流

Java 8 在 BufferedReader 类中添加了 lines() 方法,可以将文件内容转换为流,每行作为流中的一个元素。

方法介绍:

  • 方法: BufferedReader.lines()
  • 语法: public Stream<String> lines()
  • 参数: 无
  • 返回值: 返回一个 Stream<String>,包含文件中的所有行。
  • 作用: 将文件内容转换为流,便于进行文本处理。

代码示例:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class StreamFromReaderExample {
    public static void main(String[] args) {
        // 从字符串创建BufferedReader
        String text = "第一行\n第二行\n第三行\n第四行\n第五行";
        BufferedReader reader = new BufferedReader(new StringReader(text));
        
        // 使用BufferedReader.lines()创建流
        try (Stream<String> linesStream = reader.lines()) {
            System.out.println("从BufferedReader创建的流:");
            linesStream.forEach(System.out::println); // 输出文本的每一行
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        // 从文件创建BufferedReader(假设存在一个文本文件)
        try (BufferedReader fileReader = new BufferedReader(new FileReader("example.txt"));
             Stream<String> fileLines = fileReader.lines()) {
            
            System.out.println("\n从文件创建的流:");
            fileLines.forEach(System.out::println); // 输出文件的每一行
            
        } catch (IOException e) {
            System.out.println("文件读取错误: " + e.getMessage());
        }
        
        // 使用Files.lines()直接从文件创建流(Java 8的另一种方式)
        try (Stream<String> fileLines = Files.lines(Paths.get("example.txt"))) {
            
            System.out.println("\n使用Files.lines()创建的流:");
            fileLines.forEach(System.out::println); // 输出文件的每一行
            
        } catch (IOException e) {
            System.out.println("文件读取错误: " + e.getMessage());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

详细解析:

  • 基本用法:

    • BufferedReader.lines() 方法返回一个流,其中每个元素对应文件中的一行。
    • 流会按照文件中的行顺序返回元素。
    • 这个方法非常适合处理大文件,因为它不会一次性将整个文件加载到内存中。
  • 资源管理:

    • 使用 try-with-resources 语句确保流和读取器在使用后正确关闭。
    • 流关闭时会自动关闭底层的 BufferedReader。
  • 替代方法:

    • Java 8 还提供了 Files.lines(Path) 方法,可以直接从文件路径创建流。
    • Files.lines() 更简洁,不需要显式创建 BufferedReader。
  • 应用场景:

    • 文本文件处理,如日志分析、数据提取等。
    • 大文件处理,因为流式处理不需要一次性加载整个文件。
    • 文本转换和过滤,可以结合其他流操作对文本进行处理。
  • 注意事项:

    • 使用后必须关闭流,以释放底层资源。
    • 处理大文件时,流式处理比一次性读取整个文件更高效

# 4. 流与函数式接口的结合

在流操作中,只要是接收函数式接口的地方,流中的每个元素会自动传递给函数式接口的抽象方法作为参数。这是因为流的操作链条会依次处理每个元素,并将它们传递给下一个操作(比如 map, filter, forEach 等)。

换句话说,在流中,只要涉及函数式接口的地方,流会自动将元素传递给函数式接口的抽象方法。这适用于 Lambda 表达式、方法引用以及其他函数式接口的实现。

# 5. Stream 中间操作

中间操作是对流中的数据进行转换、过滤、映射、排序等操作。这些操作是延迟执行的,只有在终止操作时才会触发计算。常见的中间操作包括筛选、映射、排序等。

# 1. filter (筛选数据)

filter 是 Stream API 中的一个中间操作,用于对流中的元素进行条件过滤。它接收一个 Predicate 函数式接口作为参数,Predicate 接口的抽象方法 test(T t) 返回一个布尔值。filter 方法会依次将流中的每个元素传递给这个 Predicate,只有当 test 方法返回 true 时,该元素才会保留下来,进入到后续的处理阶段。

传统方式(不使用 Stream)

在 Java 8 之前,如果要从集合中筛选出符合特定条件的元素,通常需要使用循环和条件判断:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class TraditionalFilterExample {
    public static void main(String[] args) {
        // 创建一个包含1到10的整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // 传统方式:使用循环和条件判断筛选偶数
        List<Integer> evenNumbers = new ArrayList<>();
        for (Integer number : numbers) {
            // 判断是否为偶数
            if (number % 2 == 0) {
                evenNumbers.add(number);
            }
        }
        
        // 输出结果
        System.out.println("偶数列表:" + evenNumbers);  // 输出:[2, 4, 6, 8, 10]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

这种传统方式需要手动创建一个新的集合,然后遍历原始集合,使用条件判断筛选元素,最后将符合条件的元素添加到新集合中。代码较为冗长,且容易出错。

使用 Stream API

Java 8 引入的 Stream API 提供了一种更简洁、更优雅的方式来处理集合数据。使用 Stream API,我们可以用声明式的方式表达数据处理逻辑,而不是命令式的方式。这使得代码更加简洁、可读性更强,并且可以轻松地进行并行处理。

输入:一个带有条件判断的 Predicate。

处理:流中的每个元素都会经过这个条件判断。

输出:满足条件(即 test 方法返回 true)的元素会被保留下来,形成一个新的流。

参数和返回值

  • 参数:Predicate<T>,用于定义过滤条件,Predicate 的抽象方法是 boolean test(T t)。
  • 返回值:返回一个新的 Stream<T>,只包含满足条件的元素。

没有使用 Lambda 表达式的冗余写法

在 Java 8 之前,如果要实现一个接口,我们需要创建一个类来实现该接口,或者使用匿名内部类。例如,传统的方式实现 filter 操作如下:

// 传统方式:使用匿名内部类
List<Integer> evenNumbers = numbers.stream()
                                  .filter(new Predicate<Integer>() {
                                      @Override
                                      public boolean test(Integer n) {
                                          return n % 2 == 0;
                                      }
                                  })
                                  .collect(Collectors.toList());
1
2
3
4
5
6
7
8
9

这种写法非常冗长且不直观。Java 8 引入了 Lambda 表达式,可以大大简化函数式接口的实现。

工作原理解析

在流式操作中,filter 方法的工作原理如下:

  1. 元素传递:Stream 会自动将流中的每个元素依次传递给 Predicate 的 test 方法。
  2. 条件判断:对于流中的每个元素,test 方法会返回一个布尔值。
  3. 元素筛选:
    • 如果返回 true,该元素会被保留在新的流中。
    • 如果返回 false,该元素会被过滤掉。
  4. 延迟执行:作为中间操作,filter 不会立即执行,而是在终止操作调用时才会触发。

代码示例

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class FilterExample {
    public static void main(String[] args) {
        // 创建一个包含1到10的整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // 方式1:使用匿名内部类实现Predicate接口(传统方式)
        List<Integer> evenNumbers1 = numbers.stream()
                                           // 创建一个Predicate的匿名实现类
                                           .filter(new Predicate<Integer>() {
                                               @Override
                                               public boolean test(Integer n) {
                                                   // 判断n是否为偶数
                                                   return n % 2 == 0;
                                               }
                                           })
                                           .collect(Collectors.toList());
        
        // 方式2:使用Lambda表达式(简化方式)
        List<Integer> evenNumbers2 = numbers.stream()
                                           // Lambda表达式:n是参数,n % 2 == 0是表达式主体
                                           .filter(n -> n % 2 == 0)
                                           .collect(Collectors.toList());
        
        // 输出结果(两种方式的结果相同)
        System.out.println("方式1结果:" + evenNumbers1);  // 输出:[2, 4, 6, 8, 10]
        System.out.println("方式2结果:" + evenNumbers2);  // 输出:[2, 4, 6, 8, 10]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

详细解析

  • 匿名内部类实现:这是 Java 8 之前的传统方式,需要创建一个 Predicate 接口的匿名实现类,并重写 test 方法。这种方式代码冗长,可读性较差。

  • Lambda 表达式实现:n -> n % 2 == 0 是一个简洁的 Lambda 表达式,它实现了 Predicate<Integer> 接口的 test 方法。

    • n 是参数,代表流中的每个元素。
    • n % 2 == 0 是主体,判断 n 是否为偶数。

实际应用场景

  1. 数据清洗:过滤掉不符合业务规则的数据。
// 用户列表
List<User> users = getUsers();  // 这个方法返回用户列表

// 过滤有效用户(年龄大于0且姓名不为空)
List<User> validUsers = users.stream()
                           .filter(user -> user.getAge() > 0 && user.getName() != null)  // 定义过滤条件
                           .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
  1. 条件查询:根据特定条件查询数据。
// 商品列表
List<Product> products = getProducts();  // 这个方法返回商品列表

// 查询价格在100-3000元之间的商品
List<Product> affordableProducts = products.stream()
                                         .filter(p -> p.getPrice() >= 100 && p.getPrice() <= 3000)  // 价格范围条件
                                         .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
  1. 多条件组合过滤:组合多个条件进行筛选。
// 订单列表
List<Order> orders = getOrders();  // 这个方法返回订单列表

// 查找最近一个月内、金额大于1000元且已支付的订单
LocalDate oneMonthAgo = LocalDate.now().minusMonths(1);
List<Order> recentLargeOrders = orders.stream()
                                    .filter(order -> order.getDate().isAfter(oneMonthAgo))  // 时间条件:最近一个月
                                    .filter(order -> order.getAmount() > 1000)  // 金额条件:大于1000元
                                    .filter(Order::isPaid)  // 状态条件:已支付(使用方法引用)
                                    .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
9
10
  1. 与其他操作组合:结合其他流操作实现复杂功能。
// 学生列表
List<Student> students = getStudents();  // 这个方法返回学生列表

// 查找前5名及格的学生,并按分数降序排列
List<Student> top5PassingStudents = students.stream()
                                          .filter(student -> student.getScore() >= 60)  // 过滤及格学生
                                          .sorted((s1, s2) -> s2.getScore() - s1.getScore())  // 按分数降序排序
                                          .limit(5)  // 只取前5名
                                          .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
9
  1. 并行处理大数据集:使用并行流提高性能。
// 大量日志记录
List<LogEntry> logs = getLargeLogList();  // 假设这个方法返回大量日志

// 并行查找所有错误日志
List<LogEntry> errorLogs = logs.parallelStream()  // 使用并行流处理大数据量
                             .filter(log -> log.getLevel() == LogLevel.ERROR)  // 过滤错误级别的日志
                             .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7

# 2. map (转换数据)

map 是一个中间操作,用于将流中的每个元素映射为另一种类型。它接收一个 Function 函数式接口作为参数,Function 接口的抽象方法 apply(T t) 接受一个输入参数并返回一个结果。map 方法会将流中的每个元素传递给提供的 Function 函数中的抽象方法 apply(T t) 进行处理,并将转换后的结果作为新的流返回。

传统方式(不使用 Stream)

在 Java 8 之前,如果要将集合中的元素转换为另一种类型,通常需要使用循环和手动创建新集合:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class TraditionalMapExample {
    public static void main(String[] args) {
        // 创建一个字符串列表
        List<String> words = Arrays.asList("hello", "world", "java");
        
        // 传统方式:使用循环将字符串转换为长度
        List<Integer> wordLengths = new ArrayList<>();
        for (String word : words) {
            // 获取字符串长度并添加到新列表
            wordLengths.add(word.length());
        }
        
        // 输出结果
        System.out.println("单词长度列表:" + wordLengths);  // 输出:[5, 5, 4]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

这种传统方式需要手动创建一个新的集合,然后遍历原始集合,对每个元素进行转换,最后将转换后的结果添加到新集合中。代码较为冗长,且需要管理中间状态。

使用 Stream API

Java 8 引入的 Stream API 提供了 map 操作,使得数据转换变得更加简洁和优雅。map 操作可以将一个流中的元素转换为另一种类型,形成一个新的流,而不需要手动管理中间集合和循环逻辑。

输入:一个转换函数 Function。

处理:对流中的每个元素进行转换。

输出:将转换后的元素放入新的流中。

参数和返回值

  • 参数:Function<? super T, ? extends R>,用于定义转换逻辑,Function 的抽象方法是 R apply(T t)。
  • 返回值:返回一个新的 Stream<R>,包含转换后的元素。

没有使用 Lambda 表达式的冗余写法

在 Java 8 之前,如果要实现一个接口,我们需要创建一个类来实现该接口,或者使用匿名内部类。例如,传统的方式实现 map 操作如下:

// 传统方式:使用匿名内部类
List<Integer> wordLengths = words.stream()
                                .map(new Function<String, Integer>() {
                                    @Override
                                    public Integer apply(String s) {
                                        return s.length();
                                    }
                                })
                                .collect(Collectors.toList());
1
2
3
4
5
6
7
8
9

这种写法非常冗长且不直观。Java 8 引入了 Lambda 表达式,可以大大简化函数式接口的实现。

工作原理解析

在流式操作中,map 方法的工作原理如下:

  1. 元素传递:Stream 会自动将流中的每个元素依次传递给 Function 的 apply 方法。
  2. 转换处理:对于流中的每个元素,apply 方法会返回一个转换后的结果。
  3. 结果收集:转换后的结果会被收集到一个新的流中。
  4. 延迟执行:作为中间操作,map 不会立即执行,而是在终止操作调用时才会触发。

代码示例

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class MapExample {
    public static void main(String[] args) {
        // 创建一个字符串列表
        List<String> words = Arrays.asList("hello", "world", "java");
        
        // 方式1:使用匿名内部类实现Function接口(传统方式)
        List<Integer> wordLengths1 = words.stream()
                                         // 创建一个Function的匿名实现类
                                         .map(new Function<String, Integer>() {
                                             @Override
                                             public Integer apply(String s) {
                                                 // 获取字符串的长度
                                                 return s.length();
                                             }
                                         })
                                         .collect(Collectors.toList());
        
        // 方式2:使用Lambda表达式(简化方式)
        List<Integer> wordLengths2 = words.stream()
                                         // Lambda表达式:s是参数,s.length()是表达式主体
                                         .map(s -> s.length())
                                         .collect(Collectors.toList());
        
        // 方式3:使用方法引用(更简化的方式)
        List<Integer> wordLengths3 = words.stream()
                                         // 方法引用:String::length引用String类的length方法
                                         .map(String::length)
                                         .collect(Collectors.toList());
        
        // 输出结果(三种方式的结果相同)
        System.out.println("方式1结果:" + wordLengths1);  // 输出:[5, 5, 4]
        System.out.println("方式2结果:" + wordLengths2);  // 输出:[5, 5, 4]
        System.out.println("方式3结果:" + wordLengths3);  // 输出:[5, 5, 4]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

详细解析

  • 匿名内部类实现:这是 Java 8 之前的传统方式,需要创建一个 Function 接口的匿名实现类,并重写 apply 方法。这种方式代码冗长,可读性较差。

  • Lambda 表达式实现:s -> s.length() 是一个简洁的 Lambda 表达式,它实现了 Function<String, Integer> 接口的 apply 方法。

    • s 是参数,代表流中的每个元素。
    • s.length() 是主体,获取字符串的长度。
  • 方法引用实现:String::length 是一个方法引用,它引用了 String 类的 length 方法。方法引用是 Lambda 表达式的一种特殊形式,当 Lambda 表达式只是调用一个已有的方法时,可以使用方法引用来简化代码。

实际应用场景

  1. 数据转换:将一种类型的数据转换为另一种类型。
// 用户列表
List<User> users = getUsers();  // 这个方法返回用户列表

// 提取所有用户的姓名
List<String> userNames = users.stream()
                            .map(User::getName)  // 使用方法引用提取姓名
                            .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
  1. 数据处理:对数据进行处理或计算。
// 商品列表
List<Product> products = getProducts();  // 这个方法返回商品列表

// 计算所有商品的折扣价格(打8折)
List<Double> discountedPrices = products.stream()
                                      .map(p -> p.getPrice() * 0.8)  // 计算折扣价格
                                      .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
  1. 对象转换:将一个对象转换为另一个对象。
// 实体对象列表
List<UserEntity> userEntities = getUserEntities();  // 这个方法返回数据库实体对象

// 将实体对象转换为DTO对象(数据传输对象)
List<UserDTO> userDTOs = userEntities.stream()
                                   .map(entity -> {
                                       // 创建新的DTO对象并设置属性
                                       UserDTO dto = new UserDTO();
                                       dto.setId(entity.getId());
                                       dto.setName(entity.getName());
                                       dto.setEmail(entity.getEmail());
                                       return dto;
                                   })
                                   .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. 字符串处理:对字符串进行处理或转换。
// 字符串列表
List<String> words = Arrays.asList("hello", "world", "java");

// 将所有字符串转换为大写
List<String> upperCaseWords = words.stream()
                                 .map(String::toUpperCase)  // 使用方法引用转换为大写
                                 .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
  1. 与其他操作组合:结合其他流操作实现复杂功能。
// 订单列表
List<Order> orders = getOrders();  // 这个方法返回订单列表

// 查找所有已完成订单的金额,并按金额降序排序
List<Double> completedOrderAmounts = orders.stream()
                                         .filter(Order::isCompleted)  // 过滤已完成的订单
                                         .map(Order::getAmount)  // 提取订单金额
                                         .sorted(Comparator.reverseOrder())  // 按金额降序排序
                                         .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
9

通过 map 操作,我们可以轻松地将流中的元素从一种类型转换为另一种类型,实现数据的转换和处理。这比传统的循环方式更加简洁、可读性更强,并且可以与其他流操作无缝组合,构建强大的数据处理管道。


# 3. flatMap (展平数据)

flatMap 是一个功能更强的映射操作,它将每个元素映射为一个流,然后将所有这些流合并为一个流。它接收一个返回流的 Function 作为参数,flatMap 方法的作用是将嵌套的流结构"展开"成一个平坦的流。

传统方式(不使用 Stream)

在 Java 8 之前,如果要将嵌套的集合结构展平为单一集合,通常需要使用嵌套循环和手动创建新集合:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class TraditionalFlatMapExample {
    public static void main(String[] args) {
        // 创建一个嵌套的列表结构
        List<List<Integer>> nestedList = Arrays.asList(
                Arrays.asList(1, 2),    // 第一个子列表
                Arrays.asList(3, 4),    // 第二个子列表
                Arrays.asList(5, 6)     // 第三个子列表
        );
        
        // 传统方式:使用嵌套循环展平嵌套列表
        List<Integer> flatList = new ArrayList<>();
        for (List<Integer> innerList : nestedList) {
            for (Integer number : innerList) {
                flatList.add(number);
            }
        }
        
        // 输出结果
        System.out.println("展平后的列表:" + flatList);  // 输出:[1, 2, 3, 4, 5, 6]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

这种传统方式需要手动创建一个新的集合,然后使用嵌套循环遍历嵌套的集合结构,将每个元素添加到新集合中。代码较为冗长,且需要管理多层循环和中间状态。

使用 Stream API

Java 8 引入的 Stream API 提供了 flatMap 操作,使得处理嵌套集合变得更加简洁和优雅。flatMap 操作可以将嵌套的流结构展平为单一流,无需手动管理嵌套循环和中间集合。

输入:一个返回流的 Function。

处理:对流中的每个元素进行转换,并将结果展平成一个流。

输出:将展开后的元素放入新的流中。

参数和返回值

  • 参数:Function<? super T, ? extends Stream<? extends R>>,用于定义扁平映射逻辑,Function 的抽象方法是 Stream<R> apply(T t)。
  • 返回值:返回一个扁平化后的 Stream<R>,包含所有展开后的元素。

没有使用 Lambda 表达式的冗余写法

在 Java 8 之前,如果要实现一个接口,我们需要创建一个类来实现该接口,或者使用匿名内部类。例如,传统的方式实现 flatMap 操作如下:

// 传统方式:使用匿名内部类
Stream<Integer> flatStream = nestedList.stream()
                                      .flatMap(new Function<List<Integer>, Stream<Integer>>() {
                                          @Override
                                          public Stream<Integer> apply(List<Integer> list) {
                                              return list.stream();
                                          }
                                      });
1
2
3
4
5
6
7
8

这种写法非常冗长且不直观。Java 8 引入了 Lambda 表达式,可以大大简化函数式接口的实现。

工作原理详解

在流式操作中,flatMap 方法的工作原理如下:

  1. 元素转换为流:对于流中的每个元素,flatMap 会调用提供的函数将其转换为一个新的流。
  2. 流的合并:所有这些生成的流会被合并(扁平化)成一个单一的流。
  3. 结果收集:合并后的流包含所有原始元素生成的流中的所有元素。
  4. 延迟执行:作为中间操作,flatMap 不会立即执行,而是在终止操作调用时才会触发。

代码示例

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class FlatMapExample {
    public static void main(String[] args) {
        // 创建一个嵌套的列表结构
        List<List<Integer>> nestedList = Arrays.asList(
                Arrays.asList(1, 2),    // 第一个子列表
                Arrays.asList(3, 4),    // 第二个子列表
                Arrays.asList(5, 6)     // 第三个子列表
        );
        
        // 方式1:使用匿名内部类实现Function接口(传统方式)
        List<Integer> flatList1 = nestedList.stream()
                                          // 创建一个Function的匿名实现类
                                          .flatMap(new Function<List<Integer>, Stream<Integer>>() {
                                              @Override
                                              public Stream<Integer> apply(List<Integer> list) {
                                                  // 将列表转换为流
                                                  return list.stream();
                                              }
                                          })
                                          .collect(Collectors.toList());
        
        // 方式2:使用Lambda表达式(简化方式)
        List<Integer> flatList2 = nestedList.stream()
                                          // Lambda表达式:list是参数,list.stream()是表达式主体
                                          .flatMap(list -> list.stream())
                                          .collect(Collectors.toList());
        
        // 方式3:使用方法引用(更简化的方式)
        List<Integer> flatList3 = nestedList.stream()
                                          // 方法引用:List::stream引用List接口的stream方法
                                          .flatMap(List::stream)
                                          .collect(Collectors.toList());
        
        // 输出结果(三种方式的结果相同)
        System.out.println("方式1结果:" + flatList1);  // 输出:[1, 2, 3, 4, 5, 6]
        System.out.println("方式2结果:" + flatList2);  // 输出:[1, 2, 3, 4, 5, 6]
        System.out.println("方式3结果:" + flatList3);  // 输出:[1, 2, 3, 4, 5, 6]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

详细解析

  • 匿名内部类实现:这是 Java 8 之前的传统方式,需要创建一个 Function 接口的匿名实现类,并重写 apply 方法。这种方式代码冗长,可读性较差。

  • Lambda 表达式实现:list -> list.stream() 是一个简洁的 Lambda 表达式,它实现了 Function<List<Integer>, Stream<Integer>> 接口的 apply 方法。

    • list 是参数,代表流中的每个元素(在这个例子中是一个 List<Integer>)。
    • list.stream() 是主体,将列表转换为流。
  • 方法引用实现:List::stream 是一个方法引用,它引用了 List 接口的 stream 方法。方法引用是 Lambda 表达式的一种特殊形式,当 Lambda 表达式只是调用一个已有的方法时,可以使用方法引用来简化代码。

实际应用场景

  1. 处理嵌套集合:将嵌套的集合结构展平为单一集合。
// 每个学生有多门课程
List<Student> students = getStudents();  // 这个方法返回学生列表

// 获取所有学生的所有课程,形成一个课程列表
List<Course> allCourses = students.stream()
                                .flatMap(student -> student.getCourses().stream())  // 将每个学生的课程列表转换为流,并合并
                                .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
  1. 处理多维数组:将多维数组展平为一维数组。
// 二维数组
Integer[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// 将二维数组展平为一维数组
List<Integer> flatList = Arrays.stream(matrix)  // 将二维数组转换为Stream<Integer[]>
                             .flatMap(Arrays::stream)  // 将每个Integer[]转换为Stream<Integer>并合并
                             .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
9
10
11
  1. 处理复杂对象:从复杂对象中提取并展平多个元素。
// 订单列表,每个订单包含多个商品项
List<Order> orders = getOrders();  // 这个方法返回订单列表

// 获取所有订单中的所有商品项
List<OrderItem> allItems = orders.stream()
                               .flatMap(order -> order.getItems().stream())  // 将每个订单的商品项列表转换为流,并合并
                               .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
  1. 处理Optional值:处理可能为空的值。
// 用户列表,每个用户可能有一个地址(Optional<Address>)
List<User> users = getUsers();  // 这个方法返回用户列表

// 获取所有用户的有效地址
List<Address> validAddresses = users.stream()
                                  .map(User::getAddress)  // 获取每个用户的Optional<Address>
                                  .flatMap(optAddress -> optAddress.map(Stream::of).orElseGet(Stream::empty))  // 将Optional<Address>转换为Stream<Address>
                                  .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
  1. 字符串分割:将多个字符串分割并合并为一个单词列表。
// 句子列表
List<String> sentences = Arrays.asList(
    "Hello world",
    "Java programming",
    "Stream API"
);

// 将所有句子分割为单词,并形成一个单词列表
List<String> words = sentences.stream()
                            .flatMap(sentence -> Arrays.stream(sentence.split(" ")))  // 将每个句子分割为单词流,并合并
                            .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
9
10
11

通过 flatMap 操作,我们可以轻松地处理嵌套的集合结构,将多层次的数据展平为单一层次,便于后续处理。这比传统的嵌套循环方式更加简洁、可读性更强,并且可以与其他流操作无缝组合,构建强大的数据处理管道。


# 4. limit (限制元素数量)

limit 是一个中间操作,用于截取流中的前 N 个元素。它接收一个长整型参数,表示需要保留的元素数量,流中只会保留前 N 个元素,其余的元素会被丢弃。

传统方式(不使用 Stream)

在 Java 8 之前,如果要从集合中获取前 N 个元素,通常需要使用循环和计数器:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class TraditionalLimitExample {
    public static void main(String[] args) {
        // 创建一个包含1到10的整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // 传统方式:使用循环和计数器获取前5个元素
        List<Integer> firstFive = new ArrayList<>();
        int count = 0;
        for (Integer number : numbers) {
            if (count < 5) {
                firstFive.add(number);
                count++;
            } else {
                break;  // 已经获取了5个元素,跳出循环
            }
        }
        
        // 输出结果
        System.out.println("前5个元素:" + firstFive);  // 输出:[1, 2, 3, 4, 5]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

这种传统方式需要手动创建一个新的集合,然后使用循环和计数器来控制获取的元素数量。代码较为冗长,且需要管理计数器状态和循环控制。

使用 Stream API

Java 8 引入的 Stream API 提供了 limit 操作,使得限制元素数量变得更加简洁和优雅。limit 操作可以轻松地截取流中的前 N 个元素,无需手动管理计数器和循环控制。

输入:一个长整型数值 maxSize。

处理:截取流中的前 N 个元素。

输出:只保留前 N 个元素,形成一个新的流。

参数和返回值

  • 参数:long maxSize,指定需要保留的元素数量。
  • 返回值:返回一个新的 Stream<T>,只包含前 N 个元素。

工作原理详解

在流式操作中,limit 方法的工作原理如下:

  1. 元素计数:limit 会对流中的元素进行计数。
  2. 截取处理:当元素数量达到指定的 maxSize 值时,后续的元素会被丢弃。
  3. 形成新流:只包含前 maxSize 个元素的新流被返回。
  4. 短路特性:limit 是一个短路操作,一旦达到指定数量,就不会继续处理剩余元素,这对于无限流特别有用。
  5. 延迟执行:作为中间操作,limit 不会立即执行,而是在终止操作调用时才会触发。

代码示例

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class LimitExample {
    public static void main(String[] args) {
        // 创建一个包含1到10的整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // 使用limit操作截取前5个元素
        List<Integer> firstFive = numbers.stream()
                                        // limit(5)表示只保留流中的前5个元素
                                        .limit(5)
                                        // 收集结果到列表
                                        .collect(Collectors.toList());
        
        // 输出结果
        System.out.println("前5个元素:" + firstFive);  // 输出:[1, 2, 3, 4, 5]
        
        // 示例2:结合filter使用limit
        List<Integer> firstThreeEven = numbers.stream()
                                            // 过滤出偶数
                                            .filter(n -> n % 2 == 0)
                                            // 只取前3个偶数
                                            .limit(3)
                                            // 收集结果到列表
                                            .collect(Collectors.toList());
        
        // 输出结果
        System.out.println("前3个偶数:" + firstThreeEven);  // 输出:[2, 4, 6]
        
        // 示例3:处理无限流
        List<Integer> firstTenSquares = java.util.stream.Stream.iterate(1, n -> n + 1)  // 生成无限整数流:1, 2, 3, ...
                                                             .map(n -> n * n)  // 映射为平方数:1, 4, 9, ...
                                                             .limit(10)  // 只取前10个元素
                                                             .collect(Collectors.toList());  // 收集结果到列表
        
        // 输出结果
        System.out.println("前10个平方数:" + firstTenSquares);  // 输出:[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

详细解析

  • 基本用法:limit(5) 截取流中的前 5 个元素,后续元素被丢弃。

    • 在第一个示例中,原始流包含 1 到 10 的整数。
    • 使用 limit(5) 后,只保留了前 5 个元素:1, 2, 3, 4, 5。
    • 元素 6, 7, 8, 9, 10 被丢弃,不会出现在结果中。
  • 与过滤操作结合:先过滤元素,然后限制数量。

    • 在第二个示例中,先使用 filter 过滤出偶数:2, 4, 6, 8, 10。
    • 然后使用 limit(3) 只保留前 3 个偶数:2, 4, 6。
    • 元素 8, 10 被丢弃,不会出现在结果中。
  • 处理无限流:从无限流中获取有限数量的元素。

    • 在第三个示例中,使用 Stream.iterate 生成一个无限的整数流。
    • 使用 map 将每个整数映射为其平方。
    • 使用 limit(10) 只获取前 10 个平方数。
    • 如果没有 limit 操作,程序将陷入无限循环。

实际应用场景

  1. 分页查询:实现数据分页,只获取特定数量的数据。
// 商品列表
List<Product> products = getAllProducts();  // 这个方法返回所有商品

// 实现分页查询,获取第一页的10条数据
int pageSize = 10;
int pageNumber = 1;
List<Product> firstPage = products.stream()
                                 .skip((pageNumber - 1) * pageSize)  // 跳过前面页的数据
                                 .limit(pageSize)  // 只取pageSize条数据
                                 .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
9
10
  1. 限制结果数量:只获取满足条件的前几个结果。
// 学生列表
List<Student> students = getAllStudents();  // 这个方法返回所有学生

// 获取成绩最高的前5名学生
List<Student> topFiveStudents = students.stream()
                                      .sorted((s1, s2) -> s2.getScore() - s1.getScore())  // 按成绩降序排序
                                      .limit(5)  // 只取前5名
                                      .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
  1. 处理无限流:从无限流中获取有限数量的元素。
// 生成无限的随机数流
Stream<Double> infiniteRandoms = Stream.generate(Math::random);

// 只获取10个随机数
List<Double> tenRandoms = infiniteRandoms
                         .limit(10)  // 只取前10个元素,没有这一步会导致无限循环
                         .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
  1. 性能优化:在找到足够数量的结果后立即停止处理。
// 日志列表
List<LogEntry> logs = getLargeLogs();  // 这个方法返回大量日志

// 查找前5条错误日志
List<LogEntry> firstFiveErrors = logs.stream()
                                   .filter(log -> log.getLevel() == LogLevel.ERROR)  // 过滤错误日志
                                   .limit(5)  // 只取前5条
                                   .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
  1. 与其他操作组合:结合其他流操作实现复杂功能。
// 订单列表
List<Order> orders = getAllOrders();  // 这个方法返回所有订单

// 查找金额最高的3个已完成订单
List<Order> topThreeCompletedOrders = orders.stream()
                                          .filter(Order::isCompleted)  // 过滤已完成的订单
                                          .sorted((o1, o2) -> Double.compare(o2.getAmount(), o1.getAmount()))  // 按金额降序排序
                                          .limit(3)  // 只取前3个
                                          .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
9

通过 limit 操作,我们可以轻松地控制流中元素的数量,避免处理不必要的数据,提高程序的效率。这比传统的循环和计数器方式更加简洁、可读性更强,并且可以与其他流操作无缝组合,构建强大的数据处理管道。特别是在处理大数据集或无限流时,limit 操作的短路特性能够显著提高性能。


# 5. skip (跳过元素)

skip 是一个中间操作,用于跳过流中的前 N 个元素。它接收一个长整型参数,表示需要跳过的元素数量,流中只保留剩下的元素。

传统方式(不使用 Stream)

在 Java 8 之前,如果要跳过集合中的前 N 个元素,通常需要使用循环和计数器:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class TraditionalSkipExample {
    public static void main(String[] args) {
        // 创建一个包含1到10的整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // 传统方式:使用循环和计数器跳过前5个元素
        List<Integer> afterFive = new ArrayList<>();
        int count = 0;
        for (Integer number : numbers) {
            if (count < 5) {
                // 跳过前5个元素
                count++;
            } else {
                // 保留剩余元素
                afterFive.add(number);
            }
        }
        
        // 输出结果
        System.out.println("跳过前5个元素后:" + afterFive);  // 输出:[6, 7, 8, 9, 10]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

这种传统方式需要手动创建一个新的集合,然后使用循环和计数器来控制跳过的元素数量。代码较为冗长,且需要管理计数器状态和循环控制。

使用 Stream API

Java 8 引入的 Stream API 提供了 skip 操作,使得跳过元素变得更加简洁和优雅。skip 操作可以轻松地跳过流中的前 N 个元素,无需手动管理计数器和循环控制。

输入:一个长整型数值 n。

处理:跳过流中的前 N 个元素。

输出:跳过前 N 个元素后的新流。

参数和返回值

  • 参数:long n,指定需要跳过的元素数量。
  • 返回值:返回一个新的 Stream<T>,只包含剩下的元素。

工作原理详解

在流式操作中,skip 方法的工作原理如下:

  1. 元素计数:skip 会对流中的元素进行计数。
  2. 跳过处理:前 n 个元素会被丢弃,不会出现在结果流中。
  3. 形成新流:只包含剩余元素的新流被返回。
  4. 延迟执行:作为中间操作,skip 不会立即执行,而是在终止操作调用时才会触发。

代码示例

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class SkipExample {
    public static void main(String[] args) {
        // 创建一个包含1到10的整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // 使用skip操作跳过前5个元素
        List<Integer> afterFive = numbers.stream()
                                        // skip(5)表示跳过流中的前5个元素
                                        .skip(5)
                                        // 收集结果到列表
                                        .collect(Collectors.toList());
        
        // 输出结果
        System.out.println("跳过前5个元素后:" + afterFive);  // 输出:[6, 7, 8, 9, 10]
        
        // 示例2:结合filter使用skip
        List<Integer> oddAfterSkip = numbers.stream()
                                          // 过滤出奇数
                                          .filter(n -> n % 2 != 0)
                                          // 跳过前2个奇数
                                          .skip(2)
                                          // 收集结果到列表
                                          .collect(Collectors.toList());
        
        // 输出结果
        System.out.println("跳过前2个奇数后:" + oddAfterSkip);  // 输出:[5, 7, 9]
        
        // 示例3:结合limit使用skip实现分页
        int pageSize = 3;
        int pageNumber = 2;  // 第2页
        
        List<Integer> page2 = numbers.stream()
                                    // 跳过前一页的元素
                                    .skip((pageNumber - 1) * pageSize)
                                    // 限制当前页的元素数量
                                    .limit(pageSize)
                                    // 收集结果到列表
                                    .collect(Collectors.toList());
        
        // 输出结果
        System.out.println("第2页(每页3个元素):" + page2);  // 输出:[4, 5, 6]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

详细解析

  • 基本用法:skip(5) 跳过流中的前 5 个元素,后续元素被保留。

    • 在第一个示例中,原始流包含 1 到 10 的整数。
    • 使用 skip(5) 后,前 5 个元素(1, 2, 3, 4, 5)被跳过。
    • 只有元素 6, 7, 8, 9, 10 被保留,出现在结果中。
  • 与过滤操作结合:先过滤元素,然后跳过部分结果。

    • 在第二个示例中,先使用 filter 过滤出奇数:1, 3, 5, 7, 9。
    • 然后使用 skip(2) 跳过前 2 个奇数(1, 3)。
    • 结果只包含剩余的奇数:5, 7, 9。
  • 实现分页:结合 limit 操作实现分页功能。

    • 在第三个示例中,使用 skip((pageNumber - 1) * pageSize) 跳过前一页的元素。
    • 使用 limit(pageSize) 限制当前页的元素数量。
    • 结果是第 2 页的元素:4, 5, 6。

实际应用场景

  1. 分页查询:实现数据分页,跳过前面页的数据。
// 商品列表
List<Product> products = getAllProducts();  // 这个方法返回所有商品

// 实现分页查询,获取第二页的10条数据
int pageSize = 10;
int pageNumber = 2;
List<Product> secondPage = products.stream()
                                  .skip((pageNumber - 1) * pageSize)  // 跳过第一页的数据
                                  .limit(pageSize)  // 只取第二页的数据量
                                  .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
9
10
  1. 数据采样:跳过一部分数据后进行采样。
// 传感器数据列表
List<SensorData> sensorData = getAllSensorData();  // 这个方法返回所有传感器数据

// 跳过前1000条数据,然后每隔100条取一条
List<SensorData> sampledData = sensorData.stream()
                                       .skip(1000)  // 跳过前1000条数据
                                       .filter(data -> sensorData.indexOf(data) % 100 == 0)  // 每隔100条取一条
                                       .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
  1. 去除头部数据:去除数据集中的头部元素。
// 日志列表
List<LogEntry> logs = getAllLogs();  // 这个方法返回所有日志

// 去除前100条日志(可能是系统启动日志)
List<LogEntry> relevantLogs = logs.stream()
                                .skip(100)  // 跳过前100条日志
                                .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
  1. 与其他操作组合:结合其他流操作实现复杂功能。
// 订单列表
List<Order> orders = getAllOrders();  // 这个方法返回所有订单

// 查找排名第4到第10的高价订单
List<Order> rankFourToTen = orders.stream()
                                .sorted((o1, o2) -> Double.compare(o2.getAmount(), o1.getAmount()))  // 按金额降序排序
                                .skip(3)  // 跳过前3名
                                .limit(7)  // 只取接下来的7个
                                .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
9
  1. 数据处理:在处理大数据集时,跳过部分数据进行处理。
// 大量用户数据
List<User> users = getLargeUserList();  // 这个方法返回大量用户数据

// 跳过前10000条数据,处理接下来的1000条
List<User> processedUsers = users.stream()
                               .skip(10000)  // 跳过前10000条数据
                               .limit(1000)  // 只处理接下来的1000条
                               .map(user -> processUser(user))  // 处理用户数据
                               .collect(Collectors.toList());  // 收集结果到列表
1
2
3
4
5
6
7
8
9

通过 skip 操作,我们可以轻松地跳过流中的前 N 个元素,只处理剩余的元素。这比传统的循环和计数器方式更加简洁、可读性更强,并且可以与其他流操作无缝组合,构建强大的数据处理管道。特别是在实现分页、数据采样、去除头部数据等场景中,skip 操作能够显著简化代码并提高可维护性。与 limit 操作结合使用,可以实现更灵活的数据切片功能。


# 6. distinct (去重)

distinct 是一个中间操作,用于去除流中重复的元素。它通过元素的 hashCode() 和 equals() 方法判断元素是否重复,并保留唯一的元素。

传统方式(不使用 Stream)

在 Java 8 之前,如果要去除集合中的重复元素,通常需要使用 Set 集合或手动遍历比较:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class TraditionalDistinctExample {
    public static void main(String[] args) {
        // 创建一个包含重复元素的整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 4, 5);
        
        // 方法一:使用HashSet去重
        Set<Integer> uniqueSet = new HashSet<>(numbers);
        List<Integer> uniqueList1 = new ArrayList<>(uniqueSet);
        System.out.println("使用HashSet去重:" + uniqueList1);  // 输出:[1, 2, 3, 4, 5]
        
        // 方法二:手动遍历比较
        List<Integer> uniqueList2 = new ArrayList<>();
        for (Integer number : numbers) {
            if (!uniqueList2.contains(number)) {
                uniqueList2.add(number);
            }
        }
        System.out.println("手动遍历去重:" + uniqueList2);  // 输出:[1, 2, 3, 4, 5]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

这种传统方式需要额外的集合来存储去重后的元素,或者需要手动遍历和比较,代码较为冗长,且在大数据量时效率较低(特别是手动遍历方式)。

使用 Stream API

Java 8 引入的 Stream API 提供了 distinct 操作,使得去重变得更加简洁和优雅。distinct 操作可以轻松地去除流中的重复元素,无需额外的集合或手动比较。

输入:无。

处理:根据 hashCode() 和 equals() 去除重复元素。

输出:去除重复元素后的新流。

参数和返回值

  • 参数:无。
  • 返回值:返回一个去重后的 Stream<T>。

工作原理详解

在流式操作中,distinct 方法的工作原理如下:

  1. 元素比较:distinct 会使用元素的 hashCode() 和 equals() 方法来判断元素是否重复。
  2. 去重处理:对于重复的元素,只保留第一次出现的元素,后续重复的元素会被过滤掉。
  3. 形成新流:只包含唯一元素的新流被返回。
  4. 延迟执行:作为中间操作,distinct 不会立即执行,而是在终止操作调用时才会触发。
  5. 内部实现:内部使用类似 HashSet 的数据结构来跟踪已经出现过的元素。

代码示例

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class DistinctExample {
    public static void main(String[] args) {
        // 创建一个包含重复元素的整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 4, 5);
        
        // 使用distinct操作去除重复元素
        List<Integer> uniqueNumbers = numbers.stream()
                                           .distinct()  // 去除重复元素
                                           .collect(Collectors.toList());  // 收集结果到列表
        
        System.out.println("原始列表:" + numbers);  // 输出:[1, 2, 2, 3, 3, 3, 4, 4, 5]
        System.out.println("去重后的列表:" + uniqueNumbers);  // 输出:[1, 2, 3, 4, 5]
        
        // 示例2:处理字符串
        String text = "hello";
        List<Character> uniqueChars = text.chars()
                                        .mapToObj(c -> (char) c)  // 将int转换为Character
                                        .distinct()  // 去除重复字符
                                        .collect(Collectors.toList());  // 收集结果到列表
        
        System.out.println("原始字符串:" + text);  // 输出:hello
        System.out.println("去重后的字符:" + uniqueChars);  // 输出:[h, e, l, o]
        
        // 示例3:处理自定义对象
        List<Person> people = Arrays.asList(
            new Person("张三", 25),
            new Person("李四", 30),
            new Person("张三", 25),  // 重复的人
            new Person("王五", 35)
        );
        
        List<Person> uniquePeople = people.stream()
                                        .distinct()  // 去除重复人员
                                        .collect(Collectors.toList());  // 收集结果到列表
        
        System.out.println("原始人员列表:" + people);
        System.out.println("去重后的人员列表:" + uniquePeople);
    }
    
    // 自定义Person类,需要正确实现equals和hashCode方法
    static class Person {
        private String name;
        private int age;
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            Person person = (Person) obj;
            return age == person.age && 
                   (name == null ? person.name == null : name.equals(person.name));
        }
        
        @Override
        public int hashCode() {
            int result = name != null ? name.hashCode() : 0;
            result = 31 * result + age;
            return result;
        }
        
        @Override
        public String toString() {
            return name + "(" + age + ")";
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

详细解析

  • 基本用法:distinct() 去除流中的重复元素。

    • 在第一个示例中,原始列表包含重复的整数:1, 2, 2, 3, 3, 3, 4, 4, 5。
    • 使用 distinct() 后,重复的元素被去除,只保留了唯一的元素:1, 2, 3, 4, 5。
  • 处理字符串:将字符串转换为字符流,然后去除重复字符。

    • 在第二个示例中,字符串 "hello" 包含重复的字符 'l'。
    • 使用 distinct() 后,重复的字符被去除,只保留了唯一的字符:'h', 'e', 'l', 'o'。
  • 处理自定义对象:对于自定义对象,需要正确实现 equals 和 hashCode 方法。

    • 在第三个示例中,人员列表包含重复的人员(相同的姓名和年龄)。
    • 由于 Person 类正确实现了 equals 和 hashCode 方法,distinct() 能够识别并去除重复的人员。

实际应用场景

  1. 数据去重:去除集合中的重复元素。
// 用户ID列表,可能包含重复ID
List<Integer> userIds = Arrays.asList(101, 102, 103, 101, 104, 102, 105);

// 去除重复的用户ID
List<Integer> uniqueUserIds = userIds.stream()
                                   .distinct()  // 去除重复元素
                                   .collect(Collectors.toList());  // 收集结果到列表

System.out.println("去重后的用户ID:" + uniqueUserIds);  // 输出:[101, 102, 103, 104, 105]
1
2
3
4
5
6
7
8
9
  1. 统计唯一值:统计不同种类的元素数量。
// 产品类别列表
List<String> categories = Arrays.asList("电子", "服装", "食品", "电子", "家居", "服装", "电子");

// 统计不同产品类别的数量
long uniqueCategoryCount = categories.stream()
                                   .distinct()  // 去除重复类别
                                   .count();  // 计算唯一类别的数量

System.out.println("不同产品类别的数量:" + uniqueCategoryCount);  // 输出:4
1
2
3
4
5
6
7
8
9
  1. 去除重复对象:去除自定义对象的重复项。
// 员工列表,可能包含重复记录
List<Employee> employees = Arrays.asList(
    new Employee(1, "张三"),
    new Employee(2, "李四"),
    new Employee(1, "张三"),  // 重复的员工
    new Employee(3, "王五")
);

// 去除重复的员工记录(需要Employee类正确实现equals和hashCode方法)
List<Employee> uniqueEmployees = employees.stream()
                                        .distinct()  // 去除重复员工
                                        .collect(Collectors.toList());  // 收集结果到列表

System.out.println("去重后的员工数量:" + uniqueEmployees.size());  // 输出:3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. 与其他操作组合:结合其他流操作实现复杂功能。
// 订单列表
List<Order> orders = Arrays.asList(
    new Order(1, 101),
    new Order(2, 102),
    new Order(3, 101),
    new Order(4, 103),
    new Order(5, 102)
);

// 获取所有订单中的唯一客户ID
List<Integer> uniqueCustomerIds = orders.stream()
                                      .map(Order::getCustomerId)  // 提取客户ID
                                      .distinct()  // 去除重复客户ID
                                      .collect(Collectors.toList());  // 收集结果到列表

System.out.println("唯一客户ID:" + uniqueCustomerIds);  // 输出:[101, 102, 103]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  1. 处理字符串:去除字符串中的重复字符。
// 输入字符串
String input = "programming";

// 获取字符串中的唯一字符
String uniqueChars = input.chars()  // 将字符串转换为IntStream
                        .mapToObj(c -> (char) c)  // 将int转换为Character
                        .distinct()  // 去除重复字符
                        .map(String::valueOf)  // 将Character转换为String
                        .collect(Collectors.joining());  // 收集结果到字符串

System.out.println("原始字符串:" + input);  // 输出:programming
System.out.println("去重后的字符串:" + uniqueChars);  // 输出:progamin
1
2
3
4
5
6
7
8
9
10
11
12
  1. 数据分析:分析数据中的唯一特征。
// 销售数据列表
List<Sale> sales = Arrays.asList(
    new Sale("北京", "电子"),
    new Sale("上海", "服装"),
    new Sale("北京", "食品"),
    new Sale("广州", "电子"),
    new Sale("上海", "电子")
);

// 获取所有销售记录中的唯一城市
List<String> uniqueCities = sales.stream()
                               .map(Sale::getCity)  // 提取城市
                               .distinct()  // 去除重复城市
                               .collect(Collectors.toList());  // 收集结果到列表

System.out.println("唯一城市:" + uniqueCities);  // 输出:[北京, 上海, 广州]

// 获取所有销售记录中的唯一产品类别
List<String> uniqueCategories = sales.stream()
                                   .map(Sale::getCategory)  // 提取产品类别
                                   .distinct()  // 去除重复产品类别
                                   .collect(Collectors.toList());  // 收集结果到列表

System.out.println("唯一产品类别:" + uniqueCategories);  // 输出:[电子, 服装, 食品]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

通过 distinct 操作,我们可以轻松地去除流中的重复元素,保留唯一的元素。这比传统的使用 Set 或手动遍历比较的方式更加简洁、可读性更强,并且可以与其他流操作无缝组合,构建强大的数据处理管道。特别是在数据清洗、统计分析等场景中,distinct 操作能够显著简化代码并提高可维护性。

需要注意的是,对于自定义对象,必须正确实现 equals 和 hashCode 方法,才能确保 distinct 操作能够正确识别重复对象。如果没有正确实现这两个方法,distinct 操作可能无法正确去除重复对象。


# 7. sorted (排序)

sorted 是一个中间操作,用于对流中的元素进行排序。它有两种形式:一种是自然排序,另一种是自定义排序。

传统方式(不使用 Stream)

在 Java 8 之前,如果要对集合进行排序,通常需要使用 Collections.sort() 方法或者数组的 Arrays.sort() 方法:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class TraditionalSortExample {
    public static void main(String[] args) {
        // 创建一个字符串列表
        List<String> names = new ArrayList<>(Arrays.asList("Tom", "Jerry", "Alice", "Bob"));
        
        // 方法一:使用Collections.sort()进行自然排序
        Collections.sort(names);
        System.out.println("自然排序结果:" + names);  // 输出:[Alice, Bob, Jerry, Tom]
        
        // 重新创建列表(因为上面的排序会修改原列表)
        names = new ArrayList<>(Arrays.asList("Tom", "Jerry", "Alice", "Bob"));
        
        // 方法二:使用Collections.sort()和Comparator进行自定义排序
        Collections.sort(names, new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                return s1.length() - s2.length();
            }
        });
        System.out.println("按长度排序结果:" + names);  // 输出:[Tom, Bob, Jerry, Alice]
        
        // 对于数组,使用Arrays.sort()
        String[] nameArray = {"Tom", "Jerry", "Alice", "Bob"};
        Arrays.sort(nameArray);
        System.out.println("数组自然排序结果:" + Arrays.toString(nameArray));  // 输出:[Alice, Bob, Jerry, Tom]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

这种传统方式有几个缺点:

  1. 会直接修改原始集合,而不是创建新的集合
  2. 需要额外的匿名内部类来实现自定义排序
  3. 不能与其他操作(如过滤、映射等)无缝组合

使用 Stream API

Java 8 引入的 Stream API 提供了 sorted 操作,使得排序变得更加简洁和优雅。sorted 操作不会修改原始集合,而是创建一个新的流,可以与其他流操作无缝组合。

输入:无参数或一个 Comparator。

处理:根据自然顺序或自定义比较器对元素进行排序。

输出:排序后的新流。

参数和返回值

  • 参数:
    • sorted():不接收参数,使用元素的自然顺序进行排序。
    • sorted(Comparator<? super T> comparator):接收一个自定义比较器,用于指定排序规则。
  • 返回值:返回一个排序后的 Stream<T>。

工作原理详解

在流式操作中,sorted 方法的工作原理如下:

  1. 排序算法:sorted 使用稳定的排序算法对流中的元素进行排序。
  2. 比较逻辑:
    • 对于自然排序,使用元素的 compareTo 方法进行比较(要求元素实现 Comparable 接口)。
    • 对于自定义排序,使用提供的 Comparator 的 compare 方法进行比较。
  3. 形成新流:排序后的元素形成一个新的流。
  4. 延迟执行:作为中间操作,sorted 不会立即执行,而是在终止操作调用时才会触发。
  5. 全量操作:sorted 是一个全量操作,需要处理所有元素才能确定排序结果,因此对于大数据量可能会影响性能。

代码示例

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class SortedExample {
    public static void main(String[] args) {
        // 创建一个字符串列表
        List<String> names = Arrays.asList("Tom", "Jerry", "Alice", "Bob");
        
        // 示例1:使用自然排序(按字典顺序)
        List<String> naturalSorted = names.stream()
                                         .sorted()  // 使用自然排序
                                         .collect(Collectors.toList());  // 收集结果到列表
        
        System.out.println("原始列表:" + names);  // 输出:[Tom, Jerry, Alice, Bob]
        System.out.println("自然排序结果:" + naturalSorted);  // 输出:[Alice, Bob, Jerry, Tom]
        
        // 示例2:使用匿名内部类实现Comparator接口(传统方式)
        List<String> lengthSortedTraditional = names.stream()
                                                   .sorted(new Comparator<String>() {
                                                       @Override
                                                       public int compare(String s1, String s2) {
                                                           // 根据字符串长度进行比较
                                                           return s1.length() - s2.length();
                                                       }
                                                   })
                                                   .collect(Collectors.toList());
        
        System.out.println("使用匿名内部类的长度排序结果:" + lengthSortedTraditional);  // 输出:[Tom, Bob, Jerry, Alice]
        
        // 示例3:使用Lambda表达式(简化方式)
        List<String> lengthSortedLambda = names.stream()
                                             .sorted((s1, s2) -> s1.length() - s2.length())  // 使用Lambda表达式
                                             .collect(Collectors.toList());
        
        System.out.println("使用Lambda表达式的长度排序结果:" + lengthSortedLambda);  // 输出:[Tom, Bob, Jerry, Alice]
        
        // 示例4:使用Comparator.comparing方法(更简洁的方式)
        List<String> lengthSortedComparing = names.stream()
                                                .sorted(Comparator.comparing(String::length))  // 使用方法引用
                                                .collect(Collectors.toList());
        
        System.out.println("使用Comparator.comparing的长度排序结果:" + lengthSortedComparing);  // 输出:[Tom, Bob, Jerry, Alice]
        
        // 示例5:使用多级排序(先按长度,再按字典顺序)
        List<String> multiLevelSorted = names.stream()
                                           .sorted(Comparator.comparing(String::length)
                                                           .thenComparing(String::compareTo))  // 多级排序
                                           .collect(Collectors.toList());
        
        System.out.println("多级排序结果(先按长度,再按字典顺序):" + multiLevelSorted);  // 输出:[Bob, Tom, Alice, Jerry]
        
        // 示例6:使用反向排序
        List<String> reverseSorted = names.stream()
                                         .sorted(Comparator.reverseOrder())  // 反向自然排序
                                         .collect(Collectors.toList());
        
        System.out.println("反向自然排序结果:" + reverseSorted);  // 输出:[Tom, Jerry, Bob, Alice]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

详细解析

  • 自然排序:sorted() 按照元素的自然顺序进行排序。

    • 对于字符串,自然顺序是字典顺序(按照字符的 Unicode 值)。
    • 对于数字,自然顺序是从小到大。
    • 使用自然排序要求元素实现 Comparable 接口。
  • 匿名内部类实现:这是 Java 8 之前的传统方式,需要创建一个 Comparator 接口的匿名实现类,并重写 compare 方法。这种方式代码冗长,可读性较差。

  • Lambda 表达式实现:(s1, s2) -> s1.length() - s2.length() 是一个简洁的 Lambda 表达式,它实现了 Comparator<String> 接口的 compare 方法。

    • (s1, s2) 是参数,代表要比较的两个元素。
    • s1.length() - s2.length() 是主体,根据字符串长度进行比较。
    • 返回负数表示第一个元素小于第二个元素,返回正数表示第一个元素大于第二个元素,返回零表示两个元素相等。
  • Comparator.comparing 方法:Java 8 引入的 Comparator.comparing 方法可以更简洁地创建比较器。

    • Comparator.comparing(String::length) 创建一个根据字符串长度进行比较的比较器。
    • String::length 是方法引用,等价于 s -> s.length()。
  • 多级排序:使用 thenComparing 方法可以实现多级排序。

    • 先按第一个条件排序,如果相等,再按第二个条件排序。
    • 在示例中,先按长度排序,长度相同的再按字典顺序排序。
  • 反向排序:使用 Comparator.reverseOrder() 可以实现反向排序。

    • 对于自然排序,可以使用 Comparator.reverseOrder()。
    • 对于自定义排序,可以使用 Comparator.comparing(...).reversed()。

实际应用场景

  1. 数据排序:对集合中的元素进行排序。
// 学生列表
class Student {
    private String name;
    private int score;
    
    // 构造函数、getter和setter省略
    
    @Override
    public String toString() {
        return name + "(" + score + ")";
    }
}

List<Student> students = Arrays.asList(
    new Student("张三", 85),
    new Student("李四", 92),
    new Student("王五", 78),
    new Student("赵六", 90)
);

// 按学生成绩降序排序
List<Student> sortedByScore = students.stream()
                                    .sorted((s1, s2) -> s2.getScore() - s1.getScore())  // 按成绩降序排序
                                    .collect(Collectors.toList());

System.out.println("按成绩降序排序:" + sortedByScore);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  1. 多字段排序:根据多个字段进行排序。
// 员工类
class Employee {
    private String name;
    private String department;
    private double salary;
    
    // 构造函数、getter和setter省略
    
    @Override
    public String toString() {
        return name + "(" + department + ", " + salary + ")";
    }
}

List<Employee> employees = Arrays.asList(
    new Employee("张三", "技术部", 10000),
    new Employee("李四", "市场部", 12000),
    new Employee("王五", "技术部", 11000),
    new Employee("赵六", "市场部", 9000)
);

// 先按部门排序,再按薪资降序排序
List<Employee> sortedEmployees = employees.stream()
                                        .sorted(Comparator.comparing(Employee::getDepartment)  // 先按部门排序
                                                         .thenComparing(Employee::getSalary, Comparator.reverseOrder()))  // 再按薪资降序排序
                                        .collect(Collectors.toList());

System.out.println("先按部门排序,再按薪资降序排序:" + sortedEmployees);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  1. 自定义对象排序:对自定义对象进行排序。
// 产品类
class Product {
    private String name;
    private double price;
    private int stock;
    
    // 构造函数、getter和setter省略
    
    @Override
    public String toString() {
        return name + "(" + price + "元, 库存:" + stock + ")";
    }
}

List<Product> products = Arrays.asList(
    new Product("手机", 3999, 100),
    new Product("电脑", 5999, 50),
    new Product("耳机", 299, 200),
    new Product("平板", 2999, 80)
);

// 按产品价格从低到高排序
List<Product> sortedByPrice = products.stream()
                                    .sorted(Comparator.comparing(Product::getPrice))  // 使用Comparator.comparing方法创建比较器
                                    .collect(Collectors.toList());

System.out.println("按价格从低到高排序:" + sortedByPrice);

// 按库存从高到低排序
List<Product> sortedByStock = products.stream()
                                    .sorted(Comparator.comparing(Product::getStock).reversed())  // 使用reversed()方法实现降序排序
                                    .collect(Collectors.toList());

System.out.println("按库存从高到低排序:" + sortedByStock);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  1. 字符串排序:对字符串进行自定义排序。
// 单词列表
List<String> words = Arrays.asList("apple", "Banana", "cherry", "Date");

// 忽略大小写按字典顺序排序
List<String> sortedWords = words.stream()
                              .sorted(String.CASE_INSENSITIVE_ORDER)  // 使用String类提供的忽略大小写比较器
                              .collect(Collectors.toList());

System.out.println("忽略大小写按字典顺序排序:" + sortedWords);  // 输出:[apple, Banana, cherry, Date]

// 按字符串长度排序
List<String> sortedByLength = words.stream()
                                 .sorted(Comparator.comparing(String::length))  // 按长度排序
                                 .collect(Collectors.toList());

System.out.println("按字符串长度排序:" + sortedByLength);  // 输出:[Date, apple, cherry, Banana]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  1. 与其他操作组合:结合其他流操作实现复杂功能。
// 订单类
class Order {
    private int id;
    private double amount;
    private boolean completed;
    
    // 构造函数、getter和setter省略
    
    @Override
    public String toString() {
        return "订单" + id + "(" + amount + "元, " + (completed ? "已完成" : "未完成") + ")";
    }
}

List<Order> orders = Arrays.asList(
    new Order(1, 100.5, true),
    new Order(2, 200.0, false),
    new Order(3, 150.5, true),
    new Order(4, 300.0, true),
    new Order(5, 80.0, false)
);

// 查找前3个最高金额的已完成订单
List<Order> top3HighValueOrders = orders.stream()
                                      .filter(Order::isCompleted)  // 过滤已完成的订单
                                      .sorted(Comparator.comparing(Order::getAmount).reversed())  // 按金额降序排序
                                      .limit(3)  // 只取前3个
                                      .collect(Collectors.toList());

System.out.println("前3个最高金额的已完成订单:" + top3HighValueOrders);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  1. 数值排序:对数值进行排序。
// 整数列表
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3, 7);

// 升序排序
List<Integer> ascendingSorted = numbers.stream()
                                     .sorted()  // 自然排序(升序)
                                     .collect(Collectors.toList());

System.out.println("升序排序:" + ascendingSorted);  // 输出:[1, 2, 3, 5, 7, 8, 9]

// 降序排序
List<Integer> descendingSorted = numbers.stream()
                                      .sorted(Comparator.reverseOrder())  // 反向自然排序(降序)
                                      .collect(Collectors.toList());

System.out.println("降序排序:" + descendingSorted);  // 输出:[9, 8, 7, 5, 3, 2, 1]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

通过 sorted 操作,我们可以轻松地对流中的元素进行排序,无论是使用元素的自然顺序还是自定义的排序规则。这比传统的使用 Collections.sort() 或 Arrays.sort() 的方式更加简洁、可读性更强,并且可以与其他流操作无缝组合,构建强大的数据处理管道。特别是在数据处理、展示和分析等场景中,sorted 操作能够显著简化代码并提高可维护性。

需要注意的是,对于大数据量的排序,可能会影响性能,因为排序需要处理所有元素才能确定结果。在这种情况下,可以考虑使用并行流(parallelStream())来提高性能,或者使用其他更高效的排序算法。


# 6. Stream 终止操作

终止操作是 Stream 处理的最后一步,触发之前的中间操作,生成最终的结果。终止操作通常返回一个值(如 List、Set、Optional 等),或产生副作用(如输出、统计、归约等)。在 Stream 执行终止操作后,流会关闭,不能再进行后续操作。


# 1. collect (数据收集)

collect 是一个终止操作,用于将流中的元素收集到一个容器中,如 List、Set 或 Map,也可以用来进行字符串拼接、分组统计等复杂操作。

传统方式(不使用 Stream)

在 Java 8 之前,如果要将过滤后的元素收集到新容器中,通常需要手动创建容器并使用循环:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class TraditionalCollectExample {
    public static void main(String[] args) {
        // 创建一个水果列表,包含重复元素
        List<String> fruits = Arrays.asList("apple", "banana", "orange", "apple", "grape");
        
        // 传统方式:收集到List
        List<String> fruitList = new ArrayList<>();
        for (String fruit : fruits) {
            fruitList.add(fruit);
        }
        System.out.println("收集为List: " + fruitList);  // 输出:[apple, banana, orange, apple, grape]
        
        // 传统方式:收集到Set(去重)
        Set<String> fruitSet = new HashSet<>();
        for (String fruit : fruits) {
            fruitSet.add(fruit);
        }
        System.out.println("收集为Set: " + fruitSet);  // 输出:[orange, banana, apple, grape]
        
        // 传统方式:连接字符串
        StringBuilder joinedFruits = new StringBuilder();
        for (int i = 0; i < fruits.size(); i++) {
            joinedFruits.append(fruits.get(i));
            if (i < fruits.size() - 1) {
                joinedFruits.append(", ");
            }
        }
        System.out.println("连接后的水果: " + joinedFruits.toString());  // 输出:apple, banana, orange, apple, grape
        
        // 传统方式:按长度分组
        Map<Integer, List<String>> groupedByLength = new HashMap<>();
        for (String fruit : fruits) {
            int length = fruit.length();
            if (!groupedByLength.containsKey(length)) {
                groupedByLength.put(length, new ArrayList<>());
            }
            groupedByLength.get(length).add(fruit);
        }
        System.out.println("按长度分组: " + groupedByLength);  // 输出:{5=[apple, grape], 6=[banana, orange, apple]}
        
        // 传统方式:统计每种水果的数量
        Map<String, Integer> fruitCounts = new HashMap<>();
        for (String fruit : fruits) {
            fruitCounts.put(fruit, fruitCounts.getOrDefault(fruit, 0) + 1);
        }
        System.out.println("水果计数: " + fruitCounts);  // 输出:{orange=1, banana=1, apple=2, grape=1}
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

这种传统方式需要手动创建容器、编写循环逻辑,代码冗长且容易出错,特别是在处理复杂的分组和统计时。

使用 Stream API

Java 8 引入的 Stream API 提供了 collect 操作,使得数据收集变得更加简洁和优雅。collect 操作可以轻松地将流中的元素收集到各种容器中,或者进行各种聚合操作。

输入:一个 Collector 接口的实现(如 Collectors.toList())。

处理:将流中的元素按照收集器的规则进行归集。

输出:返回一个收集结果,如 List、Set 或拼接后的字符串。

参数和返回值

  • 参数:Collector<? super T, A, R>,用于定义收集规则。
  • 返回值:返回一个收集后的结果,类型可以是集合、Map 或字符串等。

工作原理详解

在流式操作中,collect 方法的工作原理如下:

  1. 创建容器:收集器首先创建一个用于存放结果的容器,如 ArrayList、HashSet 等。
  2. 累积元素:将流中的每个元素按照指定的规则添加到容器中。
  3. 组合结果:如果是并行流,可能会创建多个中间容器,最后需要将这些容器合并。
  4. 返回结果:返回最终的收集结果。
  5. 终止操作:collect 是一个终止操作,会触发之前所有的中间操作执行。

代码示例

import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class CollectExample {
    public static void main(String[] args) {
        // 创建一个水果列表,包含重复元素
        List<String> fruits = Arrays.asList("apple", "banana", "orange", "apple", "grape");
        
        // 示例1:将流收集为列表
        List<String> fruitList = fruits.stream()
                                       .collect(Collectors.toList());
        System.out.println("收集为List: " + fruitList);  // 输出:[apple, banana, orange, apple, grape]
        
        // 示例2:将流收集为集合(自动去重)
        Set<String> fruitSet = fruits.stream()
                                     .collect(Collectors.toSet());
        System.out.println("收集为Set: " + fruitSet);  // 输出:[orange, banana, apple, grape]
        
        // 示例3:连接流中的字符串
        String joinedFruits = fruits.stream()
                                    .collect(Collectors.joining(", "));
        System.out.println("连接后的水果: " + joinedFruits);  // 输出:apple, banana, orange, apple, grape
        
        // 示例4:分组收集
        Map<Integer, List<String>> groupedByLength = fruits.stream()
                                                          .collect(Collectors.groupingBy(String::length));
        System.out.println("按长度分组: " + groupedByLength);  // 输出:{5=[apple, grape], 6=[banana, orange, apple]}
        
        // 示例5:计数收集
        Map<String, Long> fruitCounts = fruits.stream()
                                             .collect(Collectors.groupingBy(fruit -> fruit, Collectors.counting()));
        System.out.println("水果计数: " + fruitCounts);  // 输出:{orange=1, banana=1, apple=2, grape=1}
        
        // 示例6:收集为不可变列表
        List<String> immutableList = fruits.stream()
                                          .collect(Collectors.collectingAndThen(
                                              Collectors.toList(),
                                              List::copyOf  // Java 10及以上版本支持
                                          ));
        System.out.println("不可变列表: " + immutableList);
        
        // 示例7:统计数值
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        IntSummaryStatistics stats = numbers.stream()
                                           .collect(Collectors.summarizingInt(Integer::intValue));
        System.out.println("数值统计: " + stats);  // 输出:IntSummaryStatistics{count=5, sum=15, min=1, average=3.000000, max=5}
        
        // 示例8:分区(按条件分为两组)
        Map<Boolean, List<String>> partitioned = fruits.stream()
                                                     .collect(Collectors.partitioningBy(s -> s.length() > 5));
        System.out.println("按长度分区: " + partitioned);  // 输出:{false=[apple, grape], true=[banana, orange, apple]}
        
        // 示例9:转换为Map
        Map<String, Integer> fruitLengths = fruits.stream()
                                                 .distinct()  // 去重,避免键冲突
                                                 .collect(Collectors.toMap(
                                                     fruit -> fruit,  // 键:水果名称
                                                     String::length   // 值:水果名称的长度
                                                 ));
        System.out.println("水果名称到长度的映射: " + fruitLengths);  // 输出:{orange=6, banana=6, apple=5, grape=5}
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

详细解析

  • Collectors.toList():将流中的元素收集到一个 List 中。

    • 保留所有元素,包括重复元素。
    • 保持元素的顺序。
    • 返回的是一个 ArrayList 或类似的 List 实现。
  • Collectors.toSet():将流中的元素收集到一个 Set 中。

    • 自动去除重复元素。
    • 不保证元素的顺序。
    • 返回的是一个 HashSet 或类似的 Set 实现。
  • Collectors.joining(delimiter):将流中的元素连接成一个字符串。

    • 可以指定分隔符、前缀和后缀。
    • 只适用于 Stream<String> 或可以转换为字符串的元素流。
    • 返回一个拼接后的字符串。
  • Collectors.groupingBy(classifier):根据指定的分类函数对元素进行分组。

    • 分类函数将元素映射为分组的键。
    • 返回一个 Map,键是分类函数的结果,值是对应的元素列表。
    • 可以与其他收集器组合使用,如 counting()、summingInt() 等。
  • Collectors.counting():计算元素的数量。

    • 通常与 groupingBy() 组合使用,统计每个分组中的元素数量。
    • 返回元素的数量,类型为 Long。
  • Collectors.collectingAndThen():先使用一个收集器收集元素,然后对结果应用一个转换函数。

    • 可以用于创建不可变集合、对收集结果进行进一步处理等。
  • Collectors.summarizingInt()/summarizingLong()/summarizingDouble():对数值进行统计。

    • 返回一个统计对象,包含计数、总和、最小值、平均值、最大值等信息。
  • Collectors.partitioningBy():根据一个谓词(返回布尔值的函数)将元素分为两组。

    • 返回一个 Map<Boolean, List<T>>,键是 true 或 false,值是满足或不满足条件的元素列表。
  • Collectors.toMap():将元素收集到一个 Map 中。

    • 需要提供键提取函数和值映射函数。
    • 如果有键冲突,可以提供一个合并函数来解决冲突。

实际应用场景

  1. 收集为列表:将流中的元素收集到一个新的列表中。
// 用户类
class User {
    private int id;
    private String name;
    private boolean active;
    
    // 构造函数、getter和setter省略
    
    public boolean isActive() {
        return active;
    }
    
    @Override
    public String toString() {
        return name + "(" + id + ", " + (active ? "活跃" : "非活跃") + ")";
    }
}

// 用户列表
List<User> users = Arrays.asList(
    new User(1, "张三", true),
    new User(2, "李四", false),
    new User(3, "王五", true),
    new User(4, "赵六", false)
);

// 筛选活跃用户并收集到新列表
List<User> activeUsers = users.stream()
                            .filter(User::isActive)  // 筛选活跃用户
                            .collect(Collectors.toList());  // 收集到新列表

System.out.println("活跃用户: " + activeUsers);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  1. 收集为集合:将流中的元素收集到一个集合中,自动去重。
// 订单类
class Order {
    private int id;
    private int customerId;
    
    // 构造函数、getter和setter省略
    
    public int getCustomerId() {
        return customerId;
    }
}

// 订单列表
List<Order> orders = Arrays.asList(
    new Order(1, 101),
    new Order(2, 102),
    new Order(3, 101),
    new Order(4, 103),
    new Order(5, 102)
);

// 获取所有订单的唯一客户ID
Set<Integer> uniqueCustomerIds = orders.stream()
                                     .map(Order::getCustomerId)  // 提取客户ID
                                     .collect(Collectors.toSet());  // 收集到集合,自动去重

System.out.println("唯一客户ID: " + uniqueCustomerIds);  // 输出:[101, 102, 103]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  1. 字符串拼接:将流中的字符串元素拼接成一个字符串。
// 用户名列表
List<String> userNames = Arrays.asList("张三", "李四", "王五", "赵六");

// 将用户名拼接成一个逗号分隔的字符串
String joinedNames = userNames.stream()
                            .collect(Collectors.joining(", "));  // 使用逗号和空格作为分隔符

System.out.println("用户名列表: " + joinedNames);  // 输出:张三, 李四, 王五, 赵六

// 添加前缀和后缀
String formattedNames = userNames.stream()
                               .collect(Collectors.joining(", ", "用户列表:[", "]"));  // 添加前缀和后缀

System.out.println(formattedNames);  // 输出:用户列表:[张三, 李四, 王五, 赵六]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. 分组统计:根据某个属性对元素进行分组,并进行统计。
// 产品类
class Product {
    private String name;
    private String category;
    private double price;
    
    // 构造函数、getter和setter省略
    
    public String getCategory() {
        return category;
    }
    
    public double getPrice() {
        return price;
    }
    
    @Override
    public String toString() {
        return name + "(" + price + "元)";
    }
}

// 产品列表
List<Product> products = Arrays.asList(
    new Product("手机", "电子", 3999),
    new Product("电脑", "电子", 5999),
    new Product("T恤", "服装", 99),
    new Product("牛仔裤", "服装", 199),
    new Product("面包", "食品", 9.9),
    new Product("牛奶", "食品", 6.5)
);

// 按类别分组,并计算每个类别的平均价格
Map<String, Double> avgPriceByCategory = products.stream()
                                               .collect(Collectors.groupingBy(
                                                   Product::getCategory,  // 按产品类别分组
                                                   Collectors.averagingDouble(Product::getPrice)  // 计算每组的平均价格
                                               ));

System.out.println("各类别平均价格: " + avgPriceByCategory);
// 输出:{电子=4999.0, 服装=149.0, 食品=8.2}

// 按类别分组,并统计每个类别的产品数量
Map<String, Long> countByCategory = products.stream()
                                          .collect(Collectors.groupingBy(
                                              Product::getCategory,  // 按产品类别分组
                                              Collectors.counting()  // 计算每组的产品数量
                                          ));

System.out.println("各类别产品数量: " + countByCategory);
// 输出:{电子=2, 服装=2, 食品=2}

// 按类别分组,并收集每个类别的产品列表
Map<String, List<Product>> productsByCategory = products.stream()
                                                      .collect(Collectors.groupingBy(
                                                          Product::getCategory  // 按产品类别分组
                                                      ));

System.out.println("各类别产品列表: " + productsByCategory);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
  1. 转换为Map:将流中的元素转换为Map。
// 用户类(简化版)
class SimpleUser {
    private int id;
    private String name;
    
    // 构造函数、getter和setter省略
    
    public int getId() {
        return id;
    }
    
    public String getName() {
        return name;
    }
}

// 用户列表
List<SimpleUser> simpleUsers = Arrays.asList(
    new SimpleUser(1, "张三"),
    new SimpleUser(2, "李四"),
    new SimpleUser(3, "王五"),
    new SimpleUser(4, "赵六")
);

// 将用户列表转换为ID到用户对象的映射
Map<Integer, SimpleUser> userMap = simpleUsers.stream()
                                            .collect(Collectors.toMap(
                                                SimpleUser::getId,  // 键提取函数:用户ID
                                                user -> user,  // 值映射函数:用户对象本身
                                                (existing, replacement) -> existing  // 合并函数:处理键冲突,保留现有值
                                            ));

System.out.println("用户ID到用户对象的映射: " + userMap);

// 将用户列表转换为ID到用户名的映射
Map<Integer, String> userNameMap = simpleUsers.stream()
                                            .collect(Collectors.toMap(
                                                SimpleUser::getId,  // 键提取函数:用户ID
                                                SimpleUser::getName  // 值映射函数:用户名
                                            ));

System.out.println("用户ID到用户名的映射: " + userNameMap);
// 输出:{1=张三, 2=李四, 3=王五, 4=赵六}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  1. 复杂统计:对数值进行复杂的统计分析。
// 订单类(简化版)
class SimpleOrder {
    private int id;
    private double amount;
    
    // 构造函数、getter和setter省略
    
    public double getAmount() {
        return amount;
    }
}

// 订单列表
List<SimpleOrder> simpleOrders = Arrays.asList(
    new SimpleOrder(1, 100.5),
    new SimpleOrder(2, 200.0),
    new SimpleOrder(3, 150.5),
    new SimpleOrder(4, 300.0),
    new SimpleOrder(5, 80.0)
);

// 计算订单金额的统计信息
DoubleSummaryStatistics amountStats = simpleOrders.stream()
                                                .collect(Collectors.summarizingDouble(SimpleOrder::getAmount));

System.out.println("订单金额统计:");
System.out.println("总数: " + amountStats.getCount());
System.out.println("总金额: " + amountStats.getSum());
System.out.println("最小金额: " + amountStats.getMin());
System.out.println("平均金额: " + amountStats.getAverage());
System.out.println("最大金额: " + amountStats.getMax());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

通过 collect 操作,我们可以将流中的元素收集到各种容器中,或者进行各种聚合操作,如分组、计数、求和等。这比传统的手动创建容器和编写循环的方式更加简洁、可读性更强,并且可以与其他流操作无缝组合,构建强大的数据处理管道。特别是在处理复杂的分组和统计时,collect 操作能够显著简化代码并提高可维护性。


# 2. reduce (聚合计算)

reduce 是一个终止操作,用于将流中的元素通过累积函数组合成一个结果。它适用于求和、求积、求最值等场景,是流式编程中实现聚合计算的强大工具。

传统方式(不使用 Stream)

在 Java 8 之前,如果要对集合中的元素进行聚合计算,通常需要使用循环和变量:

import java.util.Arrays;
import java.util.List;

public class TraditionalReduceExample {
    public static void main(String[] args) {
        // 创建一个整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        // 传统方式:计算总和
        int sum = 0;
        for (Integer number : numbers) {
            sum += number;
        }
        System.out.println("总和: " + sum);  // 输出:15
        
        // 传统方式:计算乘积
        int product = 1;
        for (Integer number : numbers) {
            product *= number;
        }
        System.out.println("乘积: " + product);  // 输出:120
        
        // 传统方式:查找最大值
        int max = Integer.MIN_VALUE;
        for (Integer number : numbers) {
            if (number > max) {
                max = number;
            }
        }
        System.out.println("最大值: " + max);  // 输出:5
        
        // 传统方式:字符串拼接
        List<String> words = Arrays.asList("Hello", "World", "Java");
        StringBuilder sentence = new StringBuilder();
        for (String word : words) {
            if (sentence.length() > 0) {
                sentence.append(" ");
            }
            sentence.append(word);
        }
        System.out.println("拼接结果: " + sentence.toString());  // 输出:Hello World Java
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

这种传统方式需要手动管理累积变量和循环逻辑,代码冗长且容易出错,特别是在处理复杂的聚合逻辑时。

使用 Stream API

Java 8 引入的 Stream API 提供了 reduce 操作,使得聚合计算变得更加简洁和优雅。reduce 操作可以轻松地将流中的元素聚合成一个结果,无需手动管理累积变量和循环逻辑。

输入:一个初始值(可选)和一个 BinaryOperator 函数。

处理:依次对流中的元素进行二元运算,产生一个最终的结果。

输出:返回累积后的结果,可能是 Optional<T>。

参数和返回值

  • 参数:
    • T identity:初始值(可选)。
    • BinaryOperator<T>:一个二元操作函数,如加法、乘法等。
  • 返回值:
    • 如果提供了初始值,返回类型为 T。
    • 如果没有提供初始值,返回类型为 Optional<T>。

工作原理详解

在流式操作中,reduce 方法的工作原理如下:

  1. 初始化:如果提供了初始值,则将其作为累积值的初始状态;如果没有提供初始值,则将流中的第一个元素作为初始状态。
  2. 累积计算:依次将流中的每个元素与当前的累积值进行二元操作,生成新的累积值。
  3. 结果返回:
    • 如果提供了初始值,则返回最终的累积值。
    • 如果没有提供初始值,则返回一个 Optional,因为流可能为空。
  4. 终止操作:reduce 是一个终止操作,会触发之前所有的中间操作执行。
  5. 并行处理:reduce 操作可以在并行流中高效执行,通过分而治之的方式处理大量数据。

代码示例

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.BinaryOperator;

public class ReduceExample {
    public static void main(String[] args) {
        // 创建一个整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        // 示例1:使用匿名内部类实现BinaryOperator接口(传统方式)
        int sum1 = numbers.stream()
                         .reduce(0, new BinaryOperator<Integer>() {
                             @Override
                             public Integer apply(Integer a, Integer b) {
                                 return a + b;
                             }
                         });
        System.out.println("方式1计算总和: " + sum1);  // 输出:15
        
        // 示例2:使用Lambda表达式(简化方式)
        int sum2 = numbers.stream()
                         .reduce(0, (a, b) -> a + b);
        System.out.println("方式2计算总和: " + sum2);  // 输出:15
        
        // 示例3:使用方法引用(更简化的方式)
        int sum3 = numbers.stream()
                         .reduce(0, Integer::sum);
        System.out.println("方式3计算总和: " + sum3);  // 输出:15
        
        // 示例4:不带初始值的reduce操作,计算乘积
        Optional<Integer> product = numbers.stream()
                                          .reduce((a, b) -> a * b);
        product.ifPresent(p -> System.out.println("乘积: " + p));  // 输出:120
        
        // 示例5:使用reduce查找最大值
        Optional<Integer> max = numbers.stream()
                                      .reduce(Integer::max);
        max.ifPresent(m -> System.out.println("最大值: " + m));  // 输出:5
        
        // 示例6:使用reduce查找最小值
        Optional<Integer> min = numbers.stream()
                                      .reduce(Integer::min);
        min.ifPresent(m -> System.out.println("最小值: " + m));  // 输出:1
        
        // 示例7:字符串拼接
        List<String> words = Arrays.asList("Hello", "World", "Java");
        String sentence1 = words.stream()
                              .reduce("", (s1, s2) -> s1.isEmpty() ? s2 : s1 + " " + s2);
        System.out.println("拼接结果1: " + sentence1);  // 输出:Hello World Java
        
        // 示例8:使用带初始值的reduce进行字符串拼接(更简洁的方式)
        String sentence2 = words.stream()
                              .reduce((s1, s2) -> s1 + " " + s2)
                              .orElse("");
        System.out.println("拼接结果2: " + sentence2);  // 输出:Hello World Java
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

详细解析

  • 带初始值的 reduce:

    • reduce(0, (a, b) -> a + b) 或 reduce(0, Integer::sum):
      • 初始值为 0。
      • 对于流中的每个元素,将其与当前累积值相加。
      • 例如,对于 [1, 2, 3, 4, 5],计算过程为:
        • 初始值:0
        • 第一步:0 + 1 = 1
        • 第二步:1 + 2 = 3
        • 第三步:3 + 3 = 6
        • 第四步:6 + 4 = 10
        • 第五步:10 + 5 = 15
      • 最终结果:15
  • 不带初始值的 reduce:

    • reduce((a, b) -> a * b):
      • 没有初始值,将流中的第一个元素作为初始累积值。
      • 对于流中的剩余元素,将其与当前累积值相乘。
      • 例如,对于 [1, 2, 3, 4, 5],计算过程为:
        • 初始值:1(流中的第一个元素)
        • 第一步:1 * 2 = 2
        • 第二步:2 * 3 = 6
        • 第三步:6 * 4 = 24
        • 第四步:24 * 5 = 120
      • 最终结果:120
      • 由于没有提供初始值,如果流为空,则返回一个空的 Optional。
  • 使用方法引用:

    • reduce(Integer::max) 或 reduce(Integer::min):
      • 使用 Integer 类的静态方法作为二元操作函数。
      • Integer::max 返回两个整数中的较大值。
      • Integer::min 返回两个整数中的较小值。
      • 这种方式比使用 Lambda 表达式更简洁,可读性更高。
  • 字符串拼接:

    • reduce("", (s1, s2) -> s1.isEmpty() ? s2 : s1 + " " + s2):
      • 初始值为空字符串。
      • 对于流中的每个元素,将其与当前累积值拼接,中间添加空格。
      • 特殊处理第一个元素,避免在开头添加多余的空格。

实际应用场景

  1. 求和计算:计算集合中所有元素的总和。
// 订单类
class Order {
    private int id;
    private double amount;
    
    // 构造函数、getter和setter省略
    
    public double getAmount() {
        return amount;
    }
}

// 订单列表
List<Order> orders = Arrays.asList(
    new Order(1, 100.5),
    new Order(2, 200.0),
    new Order(3, 150.5),
    new Order(4, 300.0),
    new Order(5, 80.0)
);

// 计算所有订单的总金额
double totalAmount = orders.stream()
                          .map(Order::getAmount)  // 提取订单金额
                          .reduce(0.0, Double::sum);  // 计算总和

System.out.println("订单总金额: " + totalAmount);  // 输出:831.0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  1. 求最值:查找集合中的最大值或最小值。
// 产品类
class Product {
    private String name;
    private double price;
    
    // 构造函数、getter和setter省略
    
    public String getName() {
        return name;
    }
    
    public double getPrice() {
        return price;
    }
}

// 产品列表
List<Product> products = Arrays.asList(
    new Product("手机", 3999),
    new Product("电脑", 5999),
    new Product("耳机", 299),
    new Product("平板", 2999)
);

// 查找最贵的产品价格
Optional<Double> maxPrice = products.stream()
                                  .map(Product::getPrice)  // 提取产品价格
                                  .reduce(Double::max);  // 查找最大值

maxPrice.ifPresent(price -> System.out.println("最贵产品价格: " + price));  // 输出:5999.0

// 查找最便宜的产品
Product cheapestProduct = products.stream()
                                .reduce((p1, p2) -> p1.getPrice() < p2.getPrice() ? p1 : p2)
                                .orElse(null);

if (cheapestProduct != null) {
    System.out.println("最便宜的产品: " + cheapestProduct.getName() + ", 价格: " + cheapestProduct.getPrice());
    // 输出:最便宜的产品: 耳机, 价格: 299.0
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  1. 字符串拼接:将多个字符串拼接成一个字符串。
// 单词列表
List<String> words = Arrays.asList("Hello", "World", "Java", "Stream", "API");

// 将所有单词拼接成一个字符串,用逗号分隔
String commaSeparated = words.stream()
                           .reduce((s1, s2) -> s1 + ", " + s2)
                           .orElse("");

System.out.println("逗号分隔的单词: " + commaSeparated);  // 输出:Hello, World, Java, Stream, API

// 将所有单词拼接成一个HTML列表
String htmlList = words.stream()
                      .map(word -> "<li>" + word + "</li>")  // 将每个单词包装在HTML标签中
                      .reduce("", (s1, s2) -> s1 + s2);  // 拼接所有标签

htmlList = "<ul>" + htmlList + "</ul>";  // 添加外层标签
System.out.println("HTML列表: " + htmlList);
// 输出:<ul><li>Hello</li><li>World</li><li>Java</li><li>Stream</li><li>API</li></ul>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  1. 自定义聚合:实现复杂的聚合逻辑。
// 学生类
class Student {
    private String name;
    private int score;
    
    // 构造函数、getter和setter省略
    
    public String getName() {
        return name;
    }
    
    public int getScore() {
        return score;
    }
}

// 学生列表
List<Student> students = Arrays.asList(
    new Student("张三", 85),
    new Student("李四", 92),
    new Student("王五", 78),
    new Student("赵六", 90)
);

// 计算所有学生的平均分
double avgScore = students.stream()
                         .mapToInt(Student::getScore)  // 转换为IntStream
                         .average()  // 计算平均值
                         .orElse(0);

System.out.println("学生平均分: " + avgScore);  // 输出:86.25

// 使用reduce计算平均分
double avgScoreWithReduce = students.stream()
                                  .map(Student::getScore)
                                  .reduce(new double[]{0, 0}, (acc, score) -> {
                                      acc[0] += score;  // 累加分数
                                      acc[1]++;  // 计数器加1
                                      return acc;
                                  }, (acc1, acc2) -> {
                                      acc1[0] += acc2[0];  // 合并分数
                                      acc1[1] += acc2[1];  // 合并计数
                                      return acc1;
                                  })[0] / students.size();  // 计算平均分

System.out.println("使用reduce计算的平均分: " + avgScoreWithReduce);  // 输出:86.25
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  1. 与其他操作组合:结合其他流操作实现复杂功能。
// 订单类(简化版)
class SimpleOrder {
    private int id;
    private double amount;
    private boolean completed;
    
    // 构造函数、getter和setter省略
    
    public double getAmount() {
        return amount;
    }
    
    public boolean isCompleted() {
        return completed;
    }
}

// 订单列表
List<SimpleOrder> simpleOrders = Arrays.asList(
    new SimpleOrder(1, 100.5, true),
    new SimpleOrder(2, 200.0, false),
    new SimpleOrder(3, 150.5, true),
    new SimpleOrder(4, 300.0, true),
    new SimpleOrder(5, 80.0, false)
);

// 计算所有已完成订单的总金额
double totalCompletedAmount = simpleOrders.stream()
                                        .filter(SimpleOrder::isCompleted)  // 过滤已完成的订单
                                        .map(SimpleOrder::getAmount)  // 提取订单金额
                                        .reduce(0.0, Double::sum);  // 计算总和

System.out.println("已完成订单总金额: " + totalCompletedAmount);  // 输出:551.0

// 计算已完成订单和未完成订单的总金额差异
double completedAmount = simpleOrders.stream()
                                   .filter(SimpleOrder::isCompleted)
                                   .map(SimpleOrder::getAmount)
                                   .reduce(0.0, Double::sum);

double pendingAmount = simpleOrders.stream()
                                 .filter(order -> !order.isCompleted())
                                 .map(SimpleOrder::getAmount)
                                 .reduce(0.0, Double::sum);

System.out.println("已完成订单总金额: " + completedAmount);  // 输出:551.0
System.out.println("未完成订单总金额: " + pendingAmount);  // 输出:280.0
System.out.println("总金额差异: " + (completedAmount - pendingAmount));  // 输出:271.0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
  1. 复杂对象的聚合:对复杂对象进行聚合操作。
// 购物车项类
class CartItem {
    private String productName;
    private double price;
    private int quantity;
    
    // 构造函数、getter和setter省略
    
    public String getProductName() {
        return productName;
    }
    
    public double getPrice() {
        return price;
    }
    
    public int getQuantity() {
        return quantity;
    }
    
    // 计算项目总价
    public double getTotal() {
        return price * quantity;
    }
}

// 购物车项列表
List<CartItem> cartItems = Arrays.asList(
    new CartItem("手机", 3999, 1),
    new CartItem("耳机", 299, 2),
    new CartItem("充电器", 99, 3),
    new CartItem("手机壳", 49, 2)
);

// 计算购物车总价
double cartTotal = cartItems.stream()
                          .map(CartItem::getTotal)  // 提取每个项目的总价
                          .reduce(0.0, Double::sum);  // 计算总和

System.out.println("购物车总价: " + cartTotal);  // 输出:4992.0

// 计算购物车中商品的总数量
int totalQuantity = cartItems.stream()
                           .map(CartItem::getQuantity)  // 提取每个项目的数量
                           .reduce(0, Integer::sum);  // 计算总和

System.out.println("商品总数量: " + totalQuantity);  // 输出:8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

通过 reduce 操作,我们可以将流中的元素聚合成一个结果,实现各种复杂的计算逻辑。这比传统的使用循环和变量的方式更加简洁、可读性更强,并且可以与其他流操作无缝组合,构建强大的数据处理管道。特别是在处理复杂的聚合逻辑时,reduce 操作能够显著简化代码并提高可维护性。

reduce 操作是 Stream API 中一个非常强大的终止操作,特别适合需要将多个元素组合成单一结果的场景。它的灵活性使得我们可以实现各种聚合计算,从简单的求和、求积、求最值,到复杂的自定义聚合逻辑。


# 3. forEach (遍历操作)

forEach 是一个终止操作,用于对流中的每个元素进行处理,通常用于输出、打印或其他副作用操作。它是 Stream API 中最常用的终止操作之一。

传统方式(不使用 Stream)

在 Java 8 之前,如果要遍历集合中的元素并执行某些操作,通常需要使用 for 循环或增强型 for 循环:

import java.util.Arrays;
import java.util.List;

public class TraditionalForEachExample {
    public static void main(String[] args) {
        // 创建一个水果列表
        List<String> fruits = Arrays.asList("苹果", "香蕉", "橙子", "葡萄");
        
        // 传统方式1:使用普通for循环
        System.out.println("使用普通for循环:");
        for (int i = 0; i < fruits.size(); i++) {
            System.out.println(fruits.get(i));
        }
        
        // 传统方式2:使用增强型for循环
        System.out.println("\n使用增强型for循环:");
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
        
        // 传统方式3:使用迭代器
        System.out.println("\n使用迭代器:");
        java.util.Iterator<String> iterator = fruits.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

这些传统方式虽然可以完成遍历操作,但代码较为冗长,且不能与其他操作(如过滤、映射等)无缝组合。

使用 Stream API

Java 8 引入的 Stream API 提供了 forEach 操作,使得遍历元素变得更加简洁和优雅。forEach 操作可以轻松地对流中的每个元素执行指定的操作,无需手动管理循环控制。

输入:一个 Consumer<T>,用于处理每个元素。

处理:依次对流中的每个元素执行 Consumer 操作。

输出:没有返回值,forEach 仅用于遍历和执行副作用。

参数和返回值

  • 参数:Consumer<T>,用于定义对每个元素的处理逻辑。
  • 返回值:没有返回值,返回类型是 void。

工作原理详解

在流式操作中,forEach 方法的工作原理如下:

  1. 遍历元素:forEach 会依次遍历流中的每个元素。
  2. 执行操作:对每个元素执行指定的 Consumer 操作。
  3. 无返回值:forEach 不返回任何结果,它仅用于执行副作用操作。
  4. 终止操作:forEach 是一个终止操作,会触发之前所有的中间操作执行。
  5. 顺序执行:在顺序流中,forEach 按照流中元素的顺序执行操作。
  6. 并行执行:在并行流中,forEach 可能不按照元素的顺序执行操作。

代码示例

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public class ForEachExample {
    public static void main(String[] args) {
        // 创建一个水果列表
        List<String> fruits = Arrays.asList("苹果", "香蕉", "橙子", "葡萄");
        
        // 示例1:使用匿名内部类实现Consumer接口(传统方式)
        System.out.println("使用匿名内部类:");
        fruits.stream()
              .forEach(new Consumer<String>() {
                  @Override
                  public void accept(String fruit) {
                      System.out.println(fruit);
                  }
              });
        
        // 示例2:使用Lambda表达式(简化方式)
        System.out.println("\n使用Lambda表达式:");
        fruits.stream()
              .forEach(fruit -> System.out.println(fruit));
        
        // 示例3:使用方法引用(更简化的方式)
        System.out.println("\n使用方法引用:");
        fruits.stream()
              .forEach(System.out::println);
        
        // 示例4:直接在集合上使用forEach(不使用流)
        System.out.println("\n直接在集合上使用forEach:");
        fruits.forEach(System.out::println);
        
        // 示例5:结合filter使用forEach
        System.out.println("\n结合filter使用forEach:");
        fruits.stream()
              .filter(fruit -> fruit.length() > 2)  // 过滤长度大于2的水果
              .forEach(fruit -> System.out.println(fruit + "(长度:" + fruit.length() + ")"));
        
        // 示例6:使用forEach执行复杂操作
        System.out.println("\n使用forEach执行复杂操作:");
        fruits.forEach(fruit -> {
            String upperCase = fruit.toUpperCase();
            int length = fruit.length();
            System.out.println(upperCase + "(长度:" + length + ")");
        });
        
        // 示例7:使用并行流的forEach
        System.out.println("\n使用并行流的forEach(可能不按顺序输出):");
        fruits.parallelStream()
              .forEach(System.out::println);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

详细解析

  • 匿名内部类实现:这是 Java 8 之前的传统方式,需要创建一个 Consumer 接口的匿名实现类,并重写 accept 方法。这种方式代码冗长,可读性较差。

  • Lambda 表达式实现:fruit -> System.out.println(fruit) 是一个简洁的 Lambda 表达式,它实现了 Consumer<String> 接口的 accept 方法。

    • fruit 是参数,代表流中的每个元素。
    • System.out.println(fruit) 是主体,对每个元素执行打印操作。
  • 方法引用实现:System.out::println 是一个方法引用,它引用了 System.out 对象的 println 方法。方法引用是 Lambda 表达式的一种特殊形式,当 Lambda 表达式只是调用一个已有的方法时,可以使用方法引用来简化代码。

  • 直接在集合上使用 forEach:从 Java 8 开始,Collection 接口也提供了 forEach 方法,可以直接在集合上调用,不需要先转换为流。这种方式更简洁,适用于只需要遍历元素而不需要其他流操作的场景。

  • 结合其他流操作:forEach 可以与其他流操作(如 filter、map 等)结合使用,构建强大的数据处理管道。在示例中,我们先使用 filter 过滤长度大于 2 的水果,然后使用 forEach 打印结果。

  • 执行复杂操作:forEach 的 Lambda 表达式可以包含多条语句,用大括号 {} 括起来。在示例中,我们对每个水果执行了转大写和计算长度的操作,然后打印结果。

  • 并行流的 forEach:在并行流中,forEach 可能不按照元素的顺序执行操作。如果需要保证顺序,可以使用 forEachOrdered 方法。

实际应用场景

  1. 打印输出:输出集合中的元素。
// 用户类
class User {
    private int id;
    private String name;
    private String email;
    
    // 构造函数、getter和setter省略
    
    @Override
    public String toString() {
        return "用户[ID=" + id + ", 姓名=" + name + ", 邮箱=" + email + "]";
    }
}

// 用户列表
List<User> users = Arrays.asList(
    new User(1, "张三", "zhangsan@example.com"),
    new User(2, "李四", "lisi@example.com"),
    new User(3, "王五", "wangwu@example.com")
);

// 打印所有用户信息
System.out.println("用户列表:");
users.forEach(user -> {
    System.out.println("ID: " + user.getId());
    System.out.println("姓名: " + user.getName());
    System.out.println("邮箱: " + user.getEmail());
    System.out.println("-------------------");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  1. 更新状态:修改集合中元素的状态。
// 订单类
class Order {
    private int id;
    private boolean processed;
    private LocalDateTime processedTime;
    
    // 构造函数、getter和setter省略
    
    public boolean isProcessed() {
        return processed;
    }
    
    public void setProcessed(boolean processed) {
        this.processed = processed;
    }
    
    public void setProcessedTime(LocalDateTime processedTime) {
        this.processedTime = processedTime;
    }
    
    @Override
    public String toString() {
        return "订单[ID=" + id + ", 已处理=" + processed + 
               (processedTime != null ? ", 处理时间=" + processedTime : "") + "]";
    }
}

// 订单列表
List<Order> orders = Arrays.asList(
    new Order(1, false, null),
    new Order(2, true, LocalDateTime.now().minusDays(1)),
    new Order(3, false, null),
    new Order(4, false, null)
);

// 将所有未处理的订单标记为已处理
System.out.println("处理前的订单:");
orders.forEach(System.out::println);

orders.stream()
      .filter(order -> !order.isProcessed())  // 过滤未处理的订单
      .forEach(order -> {
          order.setProcessed(true);  // 标记为已处理
          order.setProcessedTime(LocalDateTime.now());  // 设置处理时间
      });

System.out.println("\n处理后的订单:");
orders.forEach(System.out::println);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
  1. 发送通知:对每个元素执行一个操作,如发送通知。
// 消息类
class Message {
    private int id;
    private String content;
    private String recipient;
    private MessageStatus status;
    
    // 构造函数、getter和setter省略
    
    public void setStatus(MessageStatus status) {
        this.status = status;
    }
    
    @Override
    public String toString() {
        return "消息[ID=" + id + ", 内容=" + content + 
               ", 接收者=" + recipient + ", 状态=" + status + "]";
    }
}

enum MessageStatus {
    PENDING, SENT, FAILED
}

// 消息服务接口
interface NotificationService {
    void send(Message message);
}

// 消息服务实现
class EmailNotificationService implements NotificationService {
    @Override
    public void send(Message message) {
        // 模拟发送消息
        System.out.println("发送消息到 " + message.getRecipient() + ": " + message.getContent());
        // 实际应用中,这里会调用邮件发送API
    }
}

// 消息列表
List<Message> messages = Arrays.asList(
    new Message(1, "您的订单已发货", "user1@example.com", MessageStatus.PENDING),
    new Message(2, "您的账户已激活", "user2@example.com", MessageStatus.PENDING),
    new Message(3, "密码重置通知", "user3@example.com", MessageStatus.PENDING)
);

// 创建通知服务
NotificationService notificationService = new EmailNotificationService();

// 发送所有待发送的消息
System.out.println("发送消息:");
messages.stream()
        .filter(message -> message.getStatus() == MessageStatus.PENDING)  // 过滤待发送的消息
        .forEach(message -> {
            try {
                notificationService.send(message);  // 发送消息
                message.setStatus(MessageStatus.SENT);  // 更新消息状态为已发送
            } catch (Exception e) {
                message.setStatus(MessageStatus.FAILED);  // 发送失败时更新状态
                System.err.println("发送消息失败: " + e.getMessage());
            }
        });

System.out.println("\n发送后的消息状态:");
messages.forEach(System.out::println);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
  1. 记录日志:记录操作日志。
// 操作记录类
class Operation {
    private String type;
    private LocalDateTime time;
    private int userId;
    
    // 构造函数、getter和setter省略
    
    public String getType() {
        return type;
    }
    
    public LocalDateTime getTime() {
        return time;
    }
    
    public int getUserId() {
        return userId;
    }
}

// 日志接口
interface Logger {
    void info(String message, Object... args);
}

// 日志实现
class ConsoleLogger implements Logger {
    @Override
    public void info(String message, Object... args) {
        // 简单替换占位符
        String formattedMessage = message;
        for (Object arg : args) {
            formattedMessage = formattedMessage.replaceFirst("\\{\\}", arg.toString());
        }
        System.out.println("[INFO] " + formattedMessage);
    }
}

// 操作记录列表
List<Operation> operations = Arrays.asList(
    new Operation("登录", LocalDateTime.now().minusHours(2), 101),
    new Operation("查询", LocalDateTime.now().minusHours(1), 102),
    new Operation("更新", LocalDateTime.now().minusMinutes(30), 101),
    new Operation("删除", LocalDateTime.now().minusMinutes(10), 103)
);

// 创建日志记录器
Logger logger = new ConsoleLogger();

// 记录所有操作的日志
System.out.println("记录操作日志:");
operations.forEach(operation -> {
    logger.info("操作: {}, 时间: {}, 用户ID: {}",
                operation.getType(),
                operation.getTime(),
                operation.getUserId());
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
  1. 与其他操作组合:结合其他流操作实现复杂功能。
// 产品类
class Product {
    private String name;
    private double price;
    private boolean onSale;
    
    // 构造函数、getter和setter省略
    
    public double getPrice() {
        return price;
    }
    
    public void setPrice(double price) {
        this.price = price;
    }
    
    public void setOnSale(boolean onSale) {
        this.onSale = onSale;
    }
    
    @Override
    public String toString() {
        return "产品[名称=" + name + ", 价格=" + price + 
               ", 促销=" + (onSale ? "是" : "否") + "]";
    }
}

// 产品列表
List<Product> products = Arrays.asList(
    new Product("手机", 3999, false),
    new Product("电脑", 5999, false),
    new Product("耳机", 299, false),
    new Product("平板", 2999, false)
);

// 对所有价格大于1000的产品应用折扣
System.out.println("应用折扣前的产品:");
products.forEach(System.out::println);

products.stream()
        .filter(product -> product.getPrice() > 1000)  // 过滤价格大于1000的产品
        .forEach(product -> {
            double discountedPrice = product.getPrice() * 0.9;  // 计算折扣价(9折)
            product.setPrice(discountedPrice);  // 更新价格
            product.setOnSale(true);  // 标记为促销商品
        });

System.out.println("\n应用折扣后的产品:");
products.forEach(System.out::println);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
  1. 批量处理:对大量数据进行批量处理。
// 数据处理接口
interface DataProcessor {
    void process(String data);
}

// 数据处理实现
class SimpleDataProcessor implements DataProcessor {
    @Override
    public void process(String data) {
        // 模拟数据处理
        System.out.println("处理数据: " + data);
        // 实际应用中,这里会进行实际的数据处理
    }
}

// 大量数据
List<String> largeDataSet = new ArrayList<>();
for (int i = 1; i <= 1000; i++) {
    largeDataSet.add("数据项" + i);
}

// 创建数据处理器
DataProcessor dataProcessor = new SimpleDataProcessor();

// 使用并行流批量处理数据
System.out.println("批量处理数据(只显示前5条):");
largeDataSet.parallelStream()
            .limit(5)  // 为了演示,只处理前5条
            .forEach(dataProcessor::process);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

通过 forEach 操作,我们可以轻松地遍历流中的每个元素,并对其执行各种操作。这比传统的使用循环的方式更加简洁、可读性更强,并且可以与其他流操作无缝组合,构建强大的数据处理管道。特别是在执行副作用操作(如打印、更新状态、发送通知等)时,forEach 操作能够显著简化代码并提高可维护性。

需要注意的是,由于 forEach 是一个终止操作,它会触发之前所有的中间操作执行,并且在执行完毕后,流就被消费掉了,不能再次使用。如果需要在并行流中保持元素的顺序,应该使用 forEachOrdered 方法代替 forEach。


# 4. count (统计数据)

count 是一个终止操作,用于返回流中元素的个数。它是一个简单但非常实用的计数方法,没有参数,返回值是 long 类型。

传统方式(不使用 Stream)

在 Java 8 之前,如果要统计集合中的元素数量,通常有以下几种方式:

import java.util.Arrays;
import java.util.List;

public class TraditionalCountExample {
    public static void main(String[] args) {
        // 创建一个水果列表
        List<String> fruits = Arrays.asList("苹果", "香蕉", "橙子", "葡萄", "猕猴桃");
        
        // 传统方式1:直接使用集合的size()方法
        int totalCount1 = fruits.size();
        System.out.println("水果总数量(size方法): " + totalCount1);  // 输出:5
        
        // 传统方式2:使用计数器变量
        int totalCount2 = 0;
        for (String fruit : fruits) {
            totalCount2++;
        }
        System.out.println("水果总数量(计数器): " + totalCount2);  // 输出:5
        
        // 传统方式3:统计满足条件的元素数量
        int countStartWithX = 0;
        for (String fruit : fruits) {
            if (fruit.startsWith("香")) {
                countStartWithX++;
            }
        }
        System.out.println("以'香'开头的水果数量: " + countStartWithX);  // 输出:1
        
        // 传统方式4:统计长度大于2的水果数量
        int countLongNames = 0;
        for (String fruit : fruits) {
            if (fruit.length() > 2) {
                countLongNames++;
            }
        }
        System.out.println("名称长度大于2的水果数量: " + countLongNames);  // 输出:2
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

这些传统方式虽然可以完成计数操作,但代码较为冗长,特别是在需要统计满足特定条件的元素数量时,需要手动编写循环和条件判断。

使用 Stream API

Java 8 引入的 Stream API 提供了 count 操作,使得计数变得更加简洁和优雅。count 操作可以轻松地统计流中的元素数量,无需手动管理计数器和循环控制。

输入:无参数。

处理:统计流中元素的数量。

输出:返回流中元素的数量,类型为 long。

参数和返回值

  • 参数:无。
  • 返回值:long,表示流中元素的数量。

工作原理详解

在流式操作中,count 方法的工作原理如下:

  1. 遍历元素:count 会遍历流中的所有元素。
  2. 计数处理:对每个元素进行计数,累加得到总数。
  3. 返回结果:返回计数结果,类型为 long。
  4. 终止操作:count 是一个终止操作,会触发之前所有的中间操作执行。
  5. 并行处理:在并行流中,count 操作会被分解为多个子任务,每个子任务统计一部分元素,最后合并结果。

代码示例

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class CountExample {
    public static void main(String[] args) {
        // 创建一个水果列表
        List<String> fruits = Arrays.asList("苹果", "香蕉", "橙子", "葡萄", "猕猴桃");
        
        // 示例1:统计所有元素的数量
        long totalCount = fruits.stream()
                               .count();
        System.out.println("水果总数量: " + totalCount);  // 输出:5
        
        // 示例2:统计以'香'开头的水果数量
        long countStartWithX = fruits.stream()
                                    .filter(fruit -> fruit.startsWith("香"))
                                    .count();
        System.out.println("以'香'开头的水果数量: " + countStartWithX);  // 输出:1
        
        // 示例3:统计长度大于2的水果数量
        long countLongNames = fruits.stream()
                                   .filter(fruit -> fruit.length() > 2)
                                   .count();
        System.out.println("名称长度大于2的水果数量: " + countLongNames);  // 输出:2
        
        // 示例4:使用并行流统计
        long parallelCount = fruits.parallelStream()
                                  .count();
        System.out.println("使用并行流统计总数量: " + parallelCount);  // 输出:5
        
        // 示例5:结合map和count
        long countWithMap = fruits.stream()
                                 .map(String::length)  // 转换为长度
                                 .filter(length -> length > 2)  // 过滤长度大于2的
                                 .count();  // 统计数量
        System.out.println("长度大于2的水果数量(使用map): " + countWithMap);  // 输出:2
        
        // 示例6:使用Collectors.counting()进行分组计数
        Map<Integer, Long> countByLength = fruits.stream()
                                               .collect(Collectors.groupingBy(
                                                   String::length,  // 按长度分组
                                                   Collectors.counting()  // 统计每组的数量
                                               ));
        System.out.println("按长度分组的水果数量: " + countByLength);  // 输出:{2=3, 3=2}
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

详细解析

  • 基本用法:count() 方法直接返回流中的元素数量。

    • 在第一个示例中,我们直接统计了原始流中的所有元素,结果为 5。
  • 与过滤操作结合:先使用 filter 过滤元素,然后统计过滤后的元素数量。

    • 在第二个示例中,我们先过滤出以 '香' 开头的水果,然后统计数量,结果为 1(只有 "香蕉")。
    • 在第三个示例中,我们先过滤出名称长度大于 2 的水果,然后统计数量,结果为 2("猕猴桃" 和 "香蕉")。
  • 并行流统计:使用并行流可以提高大数据量下的统计效率。

    • 在第四个示例中,我们使用 parallelStream() 创建并行流,然后统计元素数量。
  • 与映射操作结合:先使用 map 转换元素,然后统计满足条件的元素数量。

    • 在第五个示例中,我们先将水果名称转换为长度,然后过滤出长度大于 2 的,最后统计数量。
  • 分组计数:使用 Collectors.groupingBy 和 Collectors.counting 进行分组计数。

    • 在第六个示例中,我们按水果名称的长度进行分组,然后统计每组的数量。

实际应用场景

  1. 简单计数:统计集合中的元素数量。
// 用户类
class User {
    private int id;
    private String name;
    private boolean active;
    
    // 构造函数、getter和setter省略
    
    public boolean isActive() {
        return active;
    }
}

// 用户列表
List<User> users = Arrays.asList(
    new User(1, "张三", true),
    new User(2, "李四", false),
    new User(3, "王五", true),
    new User(4, "赵六", false)
);

// 统计用户总数
long totalUsers = users.stream().count();
System.out.println("用户总数: " + totalUsers);  // 输出:4

// 统计活跃用户数量
long activeUsers = users.stream()
                       .filter(User::isActive)  // 过滤活跃用户
                       .count();  // 统计数量
System.out.println("活跃用户数量: " + activeUsers);  // 输出:2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  1. 条件计数:统计满足特定条件的元素数量。
// 订单类
class Order {
    private int id;
    private double amount;
    private boolean completed;
    
    // 构造函数、getter和setter省略
    
    public boolean isCompleted() {
        return completed;
    }
    
    public double getAmount() {
        return amount;
    }
}

// 订单列表
List<Order> orders = Arrays.asList(
    new Order(1, 100.5, true),
    new Order(2, 200.0, false),
    new Order(3, 150.5, true),
    new Order(4, 300.0, true),
    new Order(5, 80.0, false)
);

// 统计已完成订单的数量
long completedOrders = orders.stream()
                            .filter(Order::isCompleted)  // 过滤已完成的订单
                            .count();  // 统计数量
System.out.println("已完成订单数量: " + completedOrders);  // 输出:3

// 统计金额大于100的订单数量
long highValueOrders = orders.stream()
                            .filter(order -> order.getAmount() > 100)  // 过滤金额大于100的订单
                            .count();  // 统计数量
System.out.println("金额大于100的订单数量: " + highValueOrders);  // 输出:3

// 统计已完成且金额大于100的订单数量
long completedHighValueOrders = orders.stream()
                                     .filter(Order::isCompleted)  // 过滤已完成的订单
                                     .filter(order -> order.getAmount() > 100)  // 过滤金额大于100的订单
                                     .count();  // 统计数量
System.out.println("已完成且金额大于100的订单数量: " + completedHighValueOrders);  // 输出:2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
  1. 分组计数:按照某个属性分组后统计每组的数量。
// 产品类
class Product {
    private String name;
    private String category;
    private double price;
    
    // 构造函数、getter和setter省略
    
    public String getCategory() {
        return category;
    }
    
    public double getPrice() {
        return price;
    }
}

// 产品列表
List<Product> products = Arrays.asList(
    new Product("手机", "电子", 3999),
    new Product("电脑", "电子", 5999),
    new Product("T恤", "服装", 99),
    new Product("牛仔裤", "服装", 199),
    new Product("面包", "食品", 9.9),
    new Product("牛奶", "食品", 6.5)
);

// 统计每个类别的产品数量
Map<String, Long> productCountByCategory = products.stream()
                                                  .collect(Collectors.groupingBy(
                                                      Product::getCategory,  // 按产品类别分组
                                                      Collectors.counting()  // 统计每组的数量
                                                  ));
System.out.println("各类别产品数量: " + productCountByCategory);
// 输出:{电子=2, 服装=2, 食品=2}

// 统计每个类别中价格大于100的产品数量
Map<String, Long> expensiveProductCountByCategory = products.stream()
                                                          .filter(product -> product.getPrice() > 100)  // 过滤价格大于100的产品
                                                          .collect(Collectors.groupingBy(
                                                              Product::getCategory,  // 按产品类别分组
                                                              Collectors.counting()  // 统计每组的数量
                                                          ));
System.out.println("各类别中价格大于100的产品数量: " + expensiveProductCountByCategory);
// 输出:{电子=2, 服装=1}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  1. 复杂条件计数:统计满足多个条件的元素数量。
// 学生类
class Student {
    private String name;
    private int score;
    private double attendanceRate;
    
    // 构造函数、getter和setter省略
    
    public int getScore() {
        return score;
    }
    
    public double getAttendanceRate() {
        return attendanceRate;
    }
}

// 学生列表
List<Student> students = Arrays.asList(
    new Student("张三", 85, 0.9),
    new Student("李四", 55, 0.7),
    new Student("王五", 75, 0.85),
    new Student("赵六", 90, 0.95),
    new Student("钱七", 65, 0.8)
);

// 统计及格且出勤率高的学生数量
long goodStudents = students.stream()
                           .filter(student -> student.getScore() >= 60)  // 过滤及格的学生
                           .filter(student -> student.getAttendanceRate() > 0.8)  // 过滤出勤率高的学生
                           .count();  // 统计数量
System.out.println("及格且出勤率高的学生数量: " + goodStudents);  // 输出:3

// 统计不及格或出勤率低的学生数量
long needAttentionStudents = students.stream()
                                    .filter(student -> student.getScore() < 60 || student.getAttendanceRate() <= 0.8)  // 过滤不及格或出勤率低的学生
                                    .count();  // 统计数量
System.out.println("不及格或出勤率低的学生数量: " + needAttentionStudents);  // 输出:2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  1. 与其他操作组合:结合其他流操作实现复杂功能。
// 日志类
class LogEntry {
    private String message;
    private LogLevel level;
    private LocalDateTime timestamp;
    
    // 构造函数、getter和setter省略
    
    public LogLevel getLevel() {
        return level;
    }
    
    public LocalDateTime getTimestamp() {
        return timestamp;
    }
}

enum LogLevel {
    INFO, WARNING, ERROR
}

// 日志列表
List<LogEntry> logs = Arrays.asList(
    new LogEntry("系统启动", LogLevel.INFO, LocalDateTime.now().minusDays(2)),
    new LogEntry("用户登录", LogLevel.INFO, LocalDateTime.now().minusHours(12)),
    new LogEntry("数据库连接失败", LogLevel.ERROR, LocalDateTime.now().minusHours(6)),
    new LogEntry("文件不存在", LogLevel.WARNING, LocalDateTime.now().minusHours(4)),
    new LogEntry("内存不足", LogLevel.ERROR, LocalDateTime.now().minusHours(2)),
    new LogEntry("操作完成", LogLevel.INFO, LocalDateTime.now().minusHours(1))
);

// 统计最近一天内的错误日志数量
LocalDateTime oneDayAgo = LocalDateTime.now().minusDays(1);
long recentErrorLogs = logs.stream()
                          .filter(log -> log.getLevel() == LogLevel.ERROR)  // 过滤错误日志
                          .filter(log -> log.getTimestamp().isAfter(oneDayAgo))  // 过滤最近一天的日志
                          .count();  // 统计数量
System.out.println("最近一天内的错误日志数量: " + recentErrorLogs);  // 输出:2

// 统计各级别日志的数量
Map<LogLevel, Long> logCountByLevel = logs.stream()
                                         .collect(Collectors.groupingBy(
                                             LogEntry::getLevel,  // 按日志级别分组
                                             Collectors.counting()  // 统计每组的数量
                                         ));
System.out.println("各级别日志数量: " + logCountByLevel);
// 输出:{INFO=3, WARNING=1, ERROR=2}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
  1. 性能优化:使用并行流提高大数据量下的统计效率。
// 创建一个大数据量的列表
List<Integer> largeDataSet = new ArrayList<>();
for (int i = 1; i <= 10000000; i++) {
    largeDataSet.add(i);
}

// 使用顺序流统计
long startTime1 = System.currentTimeMillis();
long count1 = largeDataSet.stream()
                         .filter(n -> n % 2 == 0)  // 过滤偶数
                         .count();  // 统计数量
long endTime1 = System.currentTimeMillis();
System.out.println("顺序流统计偶数数量: " + count1);
System.out.println("顺序流耗时: " + (endTime1 - startTime1) + "毫秒");

// 使用并行流统计
long startTime2 = System.currentTimeMillis();
long count2 = largeDataSet.parallelStream()
                         .filter(n -> n % 2 == 0)  // 过滤偶数
                         .count();  // 统计数量
long endTime2 = System.currentTimeMillis();
System.out.println("并行流统计偶数数量: " + count2);
System.out.println("并行流耗时: " + (endTime2 - startTime2) + "毫秒");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

通过 count 操作,我们可以轻松地统计流中元素的数量,无论是全部元素还是满足特定条件的元素。这比传统的使用循环和计数器的方式更加简洁、可读性更强,并且可以与其他流操作无缝组合,构建强大的数据处理管道。特别是在数据分析、统计报告等场景中,count 操作能够显著简化代码并提高可维护性。

需要注意的是,count 是一个终止操作,它会触发之前所有的中间操作执行,并且在执行完毕后,流就被消费掉了,不能再次使用。对于大数据量的统计,可以考虑使用并行流(parallelStream())来提高性能。


# 5. anyMatch, allMatch, noneMatch (判断匹配)

这些是 Stream API 中的短路终止操作,用于判断流中的元素是否满足某些条件。它们提供了简洁高效的方式来检查集合中的元素是否符合特定条件。

传统方式(不使用 Stream)

在 Java 8 之前,如果要判断集合中的元素是否满足某些条件,通常需要使用循环和条件判断:

import java.util.Arrays;
import java.util.List;

public class TraditionalMatchExample {
    public static void main(String[] args) {
        // 创建一个整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        
        // 传统方式:检查是否存在偶数
        boolean hasEven = false;
        for (Integer number : numbers) {
            if (number % 2 == 0) {
                hasEven = true;
                break;  // 找到一个就可以停止循环
            }
        }
        System.out.println("存在偶数: " + hasEven);  // 输出:true
        
        // 传统方式:检查是否所有元素都大于0
        boolean allPositive = true;
        for (Integer number : numbers) {
            if (number <= 0) {
                allPositive = false;
                break;  // 找到一个不满足条件的就可以停止循环
            }
        }
        System.out.println("所有元素都大于0: " + allPositive);  // 输出:true
        
        // 传统方式:检查是否没有元素大于10
        boolean noneGreaterThanTen = true;
        for (Integer number : numbers) {
            if (number > 10) {
                noneGreaterThanTen = false;
                break;  // 找到一个不满足条件的就可以停止循环
            }
        }
        System.out.println("没有元素大于10: " + noneGreaterThanTen);  // 输出:true
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

这种传统方式需要手动编写循环和条件判断,代码较为冗长,且容易出错,特别是在处理复杂条件时。

使用 Stream API

Java 8 引入的 Stream API 提供了 anyMatch、allMatch 和 noneMatch 操作,使得条件匹配变得更加简洁和优雅。这些操作可以轻松地检查流中的元素是否满足特定条件,无需手动管理循环和条件判断。

  • anyMatch:只要有一个元素满足条件就返回 true。
  • allMatch:所有元素都满足条件才返回 true。
  • noneMatch:没有元素满足条件时返回 true。

输入:一个 Predicate,用于定义匹配条件。

处理:依次判断流中的每个元素是否满足条件。

输出:返回一个布尔值 true 或 false。

参数和返回值

  • 参数:Predicate<T>,用于定义匹配条件。
  • 返回值:boolean,表示是否有元素满足条件。

工作原理详解

在流式操作中,这三个方法的工作原理如下:

  1. anyMatch:

    • 依次检查流中的每个元素。
    • 只要找到一个满足条件的元素,立即返回 true,不再继续检查剩余元素。
    • 如果遍历完所有元素都没有找到满足条件的元素,返回 false。
    • 对于空流,始终返回 false。
  2. allMatch:

    • 依次检查流中的每个元素。
    • 只要找到一个不满足条件的元素,立即返回 false,不再继续检查剩余元素。
    • 如果所有元素都满足条件,返回 true。
    • 对于空流,始终返回 true(因为没有元素不满足条件)。
  3. noneMatch:

    • 依次检查流中的每个元素。
    • 只要找到一个满足条件的元素,立即返回 false,不再继续检查剩余元素。
    • 如果所有元素都不满足条件,返回 true。
    • 对于空流,始终返回 true(因为没有元素满足条件)。
  4. 短路特性:这三个方法都具有短路特性,一旦确定了结果,就不会继续处理剩余元素。这在处理大数据集或无限流时特别有用。

  5. 终止操作:这些都是终止操作,会触发之前所有的中间操作执行。

代码示例

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class MatchExample {
    public static void main(String[] args) {
        // 创建一个整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        
        // 示例1:使用匿名内部类实现Predicate接口(传统方式)
        boolean hasEven1 = numbers.stream()
                                 .anyMatch(new Predicate<Integer>() {
                                     @Override
                                     public boolean test(Integer n) {
                                         return n % 2 == 0;
                                     }
                                 });
        System.out.println("使用匿名内部类 - 存在偶数: " + hasEven1);  // 输出:true
        
        // 示例2:使用Lambda表达式(简化方式)
        
        // anyMatch示例:判断是否存在偶数
        boolean hasEven2 = numbers.stream()
                                 .anyMatch(n -> n % 2 == 0);
        System.out.println("存在偶数: " + hasEven2);  // 输出:true
        
        // allMatch示例:判断是否所有元素都大于0
        boolean allPositive = numbers.stream()
                                    .allMatch(n -> n > 0);
        System.out.println("所有元素都大于0: " + allPositive);  // 输出:true
        
        // noneMatch示例:判断是否没有元素大于10
        boolean noneGreaterThanTen = numbers.stream()
                                           .noneMatch(n -> n > 10);
        System.out.println("没有元素大于10: " + noneGreaterThanTen);  // 输出:true
        
        // 示例3:空流的行为
        List<Integer> emptyList = Arrays.asList();
        
        boolean anyMatchEmpty = emptyList.stream().anyMatch(n -> true);
        boolean allMatchEmpty = emptyList.stream().allMatch(n -> true);
        boolean noneMatchEmpty = emptyList.stream().noneMatch(n -> true);
        
        System.out.println("空流 - anyMatch: " + anyMatchEmpty);  // 输出:false
        System.out.println("空流 - allMatch: " + allMatchEmpty);  // 输出:true
        System.out.println("空流 - noneMatch: " + noneMatchEmpty);  // 输出:true
        
        // 示例4:短路特性演示
        System.out.println("短路特性演示:");
        numbers.stream()
               .peek(n -> System.out.println("处理元素: " + n))  // 用于观察处理了哪些元素
               .anyMatch(n -> n > 5);  // 一旦找到大于5的元素就返回true
        // 输出:
        // 处理元素: 1
        // 处理元素: 2
        // 处理元素: 3
        // 处理元素: 4
        // 处理元素: 5
        // 处理元素: 6
        // 找到满足条件的元素,停止处理
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

详细解析

  • anyMatch(n -> n % 2 == 0):

    • 检查流中是否存在至少一个偶数。
    • 在这个例子中,数字 2, 4, 6, 8 都是偶数,所以结果为 true。
    • 一旦找到第一个偶数(2),就立即返回 true,不再检查剩余元素。
  • allMatch(n -> n > 0):

    • 检查流中的所有元素是否都大于 0。
    • 在这个例子中,所有数字 1-9 都大于 0,所以结果为 true。
    • 如果有任何一个元素不大于 0,就会立即返回 false。
  • noneMatch(n -> n > 10):

    • 检查流中是否没有元素大于 10。
    • 在这个例子中,所有数字 1-9 都不大于 10,所以结果为 true。
    • 如果有任何一个元素大于 10,就会立即返回 false。
  • 空流的行为:

    • anyMatch 对于空流返回 false,因为没有元素满足条件。
    • allMatch 对于空流返回 true,因为没有元素不满足条件(空集合中的所有元素都满足任何条件,这是数学上的约定)。
    • noneMatch 对于空流返回 true,因为没有元素满足条件。
  • 短路特性:

    • 在示例4中,我们使用 peek 操作来观察处理了哪些元素。
    • 当使用 anyMatch(n -> n > 5) 时,一旦找到第一个大于 5 的元素(6),就立即返回 true,不再处理剩余元素(7, 8, 9)。
    • 这种短路特性可以提高处理效率,特别是在处理大数据集或无限流时。

实际应用场景

  1. 数据验证:验证数据是否满足特定条件。
// 用户类
class User {
    private String name;
    private boolean activated;
    private boolean admin;
    private boolean disabled;
    
    // 构造函数、getter和setter省略
    
    public boolean isActivated() {
        return activated;
    }
    
    public boolean isAdmin() {
        return admin;
    }
    
    public boolean isDisabled() {
        return disabled;
    }
}

// 用户列表
List<User> users = Arrays.asList(
    new User("张三", true, false, false),
    new User("李四", true, true, false),
    new User("王五", false, false, false),
    new User("赵六", true, false, true)
);

// 检查是否所有用户都已激活
boolean allActivated = users.stream()
                           .allMatch(User::isActivated);
System.out.println("所有用户都已激活: " + allActivated);  // 输出:false

// 检查是否存在管理员用户
boolean hasAdmin = users.stream()
                       .anyMatch(User::isAdmin);
System.out.println("存在管理员用户: " + hasAdmin);  // 输出:true

// 检查是否没有被禁用的用户
boolean noDisabled = users.stream()
                         .noneMatch(User::isDisabled);
System.out.println("没有被禁用的用户: " + noDisabled);  // 输出:false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
  1. 条件检查:检查集合中的元素是否满足某些业务规则。
// 订单类
class Order {
    private int id;
    private double amount;
    private boolean paid;
    private boolean cancelled;
    
    // 构造函数、getter和setter省略
    
    public double getAmount() {
        return amount;
    }
    
    public boolean isPaid() {
        return paid;
    }
    
    public boolean isCancelled() {
        return cancelled;
    }
}

// 订单列表
List<Order> orders = Arrays.asList(
    new Order(1, 500, true, false),
    new Order(2, 1200, true, false),
    new Order(3, 800, false, false),
    new Order(4, 300, true, true)
);

// 检查是否所有订单都已支付
boolean allPaid = orders.stream()
                       .allMatch(Order::isPaid);
System.out.println("所有订单都已支付: " + allPaid);  // 输出:false

// 检查是否存在超过1000元的订单
boolean hasLargeOrder = orders.stream()
                             .anyMatch(order -> order.getAmount() > 1000);
System.out.println("存在超过1000元的订单: " + hasLargeOrder);  // 输出:true

// 检查是否没有取消的订单
boolean noCancelled = orders.stream()
                           .noneMatch(Order::isCancelled);
System.out.println("没有取消的订单: " + noCancelled);  // 输出:false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
  1. 权限检查:检查用户是否具有特定权限。
// 权限类
class Permission {
    private String name;
    
    // 构造函数、getter和setter省略
    
    public Permission(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
}

// 用户权限
class UserPermissions {
    private List<Permission> permissions;
    
    // 构造函数、getter和setter省略
    
    public UserPermissions(List<Permission> permissions) {
        this.permissions = permissions;
    }
    
    public List<Permission> getPermissions() {
        return permissions;
    }
}

// 创建用户权限
List<Permission> userPermissions = Arrays.asList(
    new Permission("READ"),
    new Permission("WRITE"),
    new Permission("UPDATE")
);
UserPermissions user = new UserPermissions(userPermissions);

// 检查用户是否具有管理员权限
boolean isAdmin = user.getPermissions().stream()
                     .anyMatch(permission -> permission.getName().equals("ADMIN"));
System.out.println("用户具有管理员权限: " + isAdmin);  // 输出:false

// 检查用户是否具有所有必需权限
List<String> requiredPermissions = Arrays.asList("READ", "WRITE");
boolean hasAllRequired = requiredPermissions.stream()
                                          .allMatch(required -> 
                                              user.getPermissions().stream()
                                                  .anyMatch(p -> p.getName().equals(required))
                                          );
System.out.println("用户具有所有必需权限: " + hasAllRequired);  // 输出:true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  1. 数据筛选前的预检查:在进行复杂的数据筛选前,先检查是否有满足条件的数据。
// 产品类
class Product {
    private String name;
    private int stock;
    private int minStock;
    
    // 构造函数、getter和setter省略
    
    public Product(String name, int stock, int minStock) {
        this.name = name;
        this.stock = stock;
        this.minStock = minStock;
    }
    
    public String getName() {
        return name;
    }
    
    public int getStock() {
        return stock;
    }
    
    public int getMinStock() {
        return minStock;
    }
}

// 产品列表
List<Product> products = Arrays.asList(
    new Product("手机", 100, 20),
    new Product("电脑", 50, 10),
    new Product("耳机", 5, 10),
    new Product("平板", 30, 15)
);

// 检查是否有库存不足的产品
boolean hasLowStock = products.stream()
                             .anyMatch(product -> product.getStock() < product.getMinStock());
System.out.println("存在库存不足的产品: " + hasLowStock);  // 输出:true

// 如果有库存不足的产品,则找出这些产品
if (hasLowStock) {
    List<Product> lowStockProducts = products.stream()
                                           .filter(product -> product.getStock() < product.getMinStock())
                                           .collect(java.util.stream.Collectors.toList());
    System.out.println("库存不足的产品:");
    lowStockProducts.forEach(product -> 
        System.out.println(product.getName() + " - 当前库存: " + product.getStock() + 
                          ", 最低库存要求: " + product.getMinStock())
    );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  1. 与其他操作组合:结合其他流操作实现复杂功能。
// 学生类
class Student {
    private String name;
    private int score;
    private double attendanceRate;
    
    // 构造函数、getter和setter省略
    
    public Student(String name, int score, double attendanceRate) {
        this.name = name;
        this.score = score;
        this.attendanceRate = attendanceRate;
    }
    
    public String getName() {
        return name;
    }
    
    public int getScore() {
        return score;
    }
    
    public double getAttendanceRate() {
        return attendanceRate;
    }
}

// 学生列表
List<Student> students = Arrays.asList(
    new Student("张三", 85, 0.9),
    new Student("李四", 55, 0.7),
    new Student("王五", 75, 0.85),
    new Student("赵六", 90, 0.95),
    new Student("钱七", 65, 0.8)
);

// 检查是否所有及格的学生都有良好的出勤率
boolean allPassedWithGoodAttendance = students.stream()
                                            .filter(student -> student.getScore() >= 60)  // 过滤及格的学生
                                            .allMatch(student -> student.getAttendanceRate() > 0.8);  // 检查是否都有良好的出勤率
System.out.println("所有及格的学生都有良好的出勤率: " + allPassedWithGoodAttendance);  // 输出:true

// 检查是否有学生既不及格又出勤率低
boolean hasFailedWithPoorAttendance = students.stream()
                                            .anyMatch(student -> 
                                                student.getScore() < 60 && student.getAttendanceRate() < 0.8
                                            );
System.out.println("存在既不及格又出勤率低的学生: " + hasFailedWithPoorAttendance);  // 输出:true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
  1. 性能优化:利用短路特性提高处理效率。
// 创建一个大数据集
List<Integer> largeDataSet = new ArrayList<>();
for (int i = 1; i <= 1000000; i++) {
    largeDataSet.add(i);
}

// 使用anyMatch查找第一个大于500000的元素
long startTime = System.currentTimeMillis();
boolean hasLargeNumber = largeDataSet.stream()
                                    .anyMatch(n -> {
                                        // 模拟耗时操作
                                        try {
                                            Thread.sleep(1);
                                        } catch (InterruptedException e) {
                                            e.printStackTrace();
                                        }
                                        return n > 500000;
                                    });
long endTime = System.currentTimeMillis();

System.out.println("存在大于500000的元素: " + hasLargeNumber);  // 输出:true
System.out.println("耗时: " + (endTime - startTime) + "毫秒");  // 由于短路特性,只需处理500001个元素就可以得到结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

通过 anyMatch、allMatch 和 noneMatch 操作,我们可以轻松地检查流中的元素是否满足特定条件。这比传统的使用循环和条件判断的方式更加简洁、可读性更强,并且可以与其他流操作无缝组合,构建强大的数据处理管道。特别是在数据验证、条件检查等场景中,这些操作能够显著简化代码并提高可维护性。


# 6. findFirst, findAny (查找元素)

这两个方法用于在流中查找元素,返回一个 Optional 对象。它们是 Stream API 中非常实用的终止操作,可以帮助我们快速找到满足条件的元素。

传统方式(不使用 Stream)

在 Java 8 之前,如果要在集合中查找元素,通常需要使用循环和条件判断:

import java.util.Arrays;
import java.util.List;

public class TraditionalFindExample {
    public static void main(String[] args) {
        // 创建一个水果列表
        List<String> fruits = Arrays.asList("苹果", "香蕉", "橙子", "葡萄", "猕猴桃");
        
        // 传统方式:查找第一个元素
        String firstFruit = null;
        if (!fruits.isEmpty()) {
            firstFruit = fruits.get(0);
        }
        if (firstFruit != null) {
            System.out.println("第一个水果: " + firstFruit);  // 输出:第一个水果: 苹果
        }
        
        // 传统方式:查找第一个以"香"开头的水果
        String firstFruitWithX = null;
        for (String fruit : fruits) {
            if (fruit.startsWith("香")) {
                firstFruitWithX = fruit;
                break;  // 找到第一个就停止循环
            }
        }
        if (firstFruitWithX != null) {
            System.out.println("第一个以'香'开头的水果: " + firstFruitWithX);  // 输出:第一个以'香'开头的水果: 香蕉
        }
        
        // 传统方式:查找任意一个长度大于2的水果
        String anyLongFruit = null;
        for (String fruit : fruits) {
            if (fruit.length() > 2) {
                anyLongFruit = fruit;
                break;  // 找到一个就停止循环
            }
        }
        if (anyLongFruit != null) {
            System.out.println("任意一个长度大于2的水果: " + anyLongFruit);  // 输出:任意一个长度大于2的水果: 苹果
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

这种传统方式需要手动编写循环和条件判断,代码较为冗长,且需要额外处理空值情况,容易出错。

使用 Stream API

Java 8 引入的 Stream API 提供了 findFirst 和 findAny 操作,使得查找元素变得更加简洁和优雅。这些操作可以轻松地在流中查找元素,并通过 Optional 优雅地处理可能为空的情况。

  • findFirst:返回流中的第一个元素。
  • findAny:返回流中的任意一个元素,尤其适用于并行流。

输入:无参数。

处理:在流中查找元素。

输出:返回一个 Optional<T> 对象。

参数和返回值

  • 参数:无。
  • 返回值:Optional<T>,表示查找到的元素,可能为空。

工作原理详解

在流式操作中,这两个方法的工作原理如下:

  1. findFirst:

    • 返回流中的第一个元素。
    • 如果流是有序的,则返回按照流的顺序出现的第一个元素。
    • 如果流为空,则返回一个空的 Optional。
    • 在并行流中,需要协调各个分片以确定哪个元素是第一个,可能会影响性能。
  2. findAny:

    • 返回流中的任意一个元素,不保证是哪一个。
    • 在串行流中,通常返回第一个元素,行为类似于 findFirst。
    • 在并行流中,可以返回任何一个分片中的元素,不需要协调,性能更好。
    • 如果流为空,则返回一个空的 Optional。
  3. 短路特性:这两个方法都具有短路特性,一旦找到符合条件的元素,就不会继续处理剩余元素。

  4. 终止操作:这些都是终止操作,会触发之前所有的中间操作执行。

  5. Optional 返回值:返回 Optional 对象,可以优雅地处理可能为空的情况,避免空指针异常。

代码示例

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class FindExample {
    public static void main(String[] args) {
        // 创建一个水果列表
        List<String> fruits = Arrays.asList("苹果", "香蕉", "橙子", "葡萄", "猕猴桃");
        
        // 示例1:查找第一个元素
        Optional<String> firstFruit = fruits.stream()
                                           .findFirst();
        firstFruit.ifPresent(fruit -> System.out.println("第一个水果: " + fruit));  // 输出:第一个水果: 苹果
        
        // 示例2:查找任意一个元素
        Optional<String> anyFruit = fruits.stream()
                                         .findAny();
        anyFruit.ifPresent(fruit -> System.out.println("任意一个水果: " + fruit));  // 输出:任意一个水果: 苹果(在串行流中通常是第一个元素)
        
        // 示例3:在并行流中查找任意一个元素
        Optional<String> anyFruitParallel = fruits.parallelStream()
                                                 .findAny();
        anyFruitParallel.ifPresent(fruit -> System.out.println("并行流中的任意水果: " + fruit));  // 输出可能是任何一个水果
        
        // 示例4:查找第一个以'香'开头的水果
        Optional<String> firstXFruit = fruits.stream()
                                            .filter(fruit -> fruit.startsWith("香"))
                                            .findFirst();
        firstXFruit.ifPresent(fruit -> System.out.println("第一个以'香'开头的水果: " + fruit));  // 输出:第一个以'香'开头的水果: 香蕉
        
        // 示例5:查找任意一个长度大于2的水果
        Optional<String> anyLongFruit = fruits.stream()
                                             .filter(fruit -> fruit.length() > 2)
                                             .findAny();
        anyLongFruit.ifPresent(fruit -> System.out.println("任意一个长度大于2的水果: " + fruit));  // 输出:任意一个长度大于2的水果: 猕猴桃(在串行流中可能是第一个符合条件的元素)
        
        // 示例6:处理空流的情况
        List<String> emptyList = Arrays.asList();
        Optional<String> emptyResult = emptyList.stream().findFirst();
        System.out.println("空流的结果是否存在: " + emptyResult.isPresent());  // 输出:空流的结果是否存在: false
        
        // 示例7:使用orElse处理空结果
        String defaultFruit = emptyList.stream()
                                      .findFirst()
                                      .orElse("默认水果");
        System.out.println("默认水果: " + defaultFruit);  // 输出:默认水果: 默认水果
        
        // 示例8:短路特性演示
        System.out.println("短路特性演示:");
        fruits.stream()
              .peek(fruit -> System.out.println("处理元素: " + fruit))  // 用于观察处理了哪些元素
              .filter(fruit -> fruit.length() > 2)  // 过滤长度大于2的水果
              .findFirst();  // 找到第一个符合条件的元素后停止处理
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

详细解析

  • findFirst():

    • 返回流中的第一个元素,适用于有序流。
    • 在第一个示例中,返回了列表中的第一个元素 "苹果"。
    • 在第四个示例中,先使用 filter 过滤出以 '香' 开头的水果,然后返回第一个符合条件的元素 "香蕉"。
  • findAny():

    • 返回流中的任意元素,在并行流中表现更佳。
    • 在第二个示例中,在串行流中使用 findAny,通常会返回第一个元素 "苹果"。
    • 在第三个示例中,在并行流中使用 findAny,可能会返回任何一个水果,结果不确定。
    • 在第五个示例中,先使用 filter 过滤出长度大于 2 的水果,然后返回任意一个符合条件的元素。
  • 处理空结果:

    • 这两个方法都返回 Optional 对象,可以优雅地处理可能为空的情况。
    • 在第六个示例中,对空流调用 findFirst,返回一个空的 Optional。
    • 在第七个示例中,使用 orElse 方法为空结果提供默认值。
  • 短路特性:

    • 在第八个示例中,我们使用 peek 操作来观察处理了哪些元素。
    • 当使用 filter 和 findFirst 组合时,一旦找到第一个符合条件的元素,就不会继续处理剩余元素。

实际应用场景

  1. 查找第一个满足条件的元素:在集合中查找第一个满足特定条件的元素。
// 用户类
class User {
    private int id;
    private String name;
    private boolean admin;
    
    // 构造函数、getter和setter省略
    
    public User(int id, String name, boolean admin) {
        this.id = id;
        this.name = name;
        this.admin = admin;
    }
    
    public int getId() {
        return id;
    }
    
    public String getName() {
        return name;
    }
    
    public boolean isAdmin() {
        return admin;
    }
}

// 用户列表
List<User> users = Arrays.asList(
    new User(1, "张三", false),
    new User(2, "李四", true),
    new User(3, "王五", false),
    new User(4, "赵六", true)
);

// 查找第一个管理员用户
Optional<User> firstAdmin = users.stream()
                               .filter(User::isAdmin)  // 过滤管理员用户
                               .findFirst();  // 查找第一个符合条件的元素

// 处理查找结果
firstAdmin.ifPresent(admin -> {
    System.out.println("找到管理员: " + admin.getName());
    // 在实际应用中,这里可能会调用一个方法来通知管理员
    // notifyAdmin(admin);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  1. 快速检查是否存在满足条件的元素:使用 findAny 快速检查是否存在满足条件的元素。
// 订单类
class Order {
    private int id;
    private boolean processed;
    
    // 构造函数、getter和setter省略
    
    public Order(int id, boolean processed) {
        this.id = id;
        this.processed = processed;
    }
    
    public int getId() {
        return id;
    }
    
    public boolean isProcessed() {
        return processed;
    }
}

// 订单列表
List<Order> orders = Arrays.asList(
    new Order(1, true),
    new Order(2, true),
    new Order(3, false),
    new Order(4, true)
);

// 检查是否存在未处理的订单
boolean hasUnprocessedOrder = orders.stream()
                                   .filter(order -> !order.isProcessed())  // 过滤未处理的订单
                                   .findAny()  // 查找任意一个符合条件的元素
                                   .isPresent();  // 检查是否存在

// 根据检查结果执行操作
if (hasUnprocessedOrder) {
    System.out.println("存在未处理的订单,需要处理");
    // 在实际应用中,这里可能会调用一个方法来处理订单
    // processOrders();
} else {
    System.out.println("所有订单都已处理");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  1. 并行处理中的快速查找:在并行流中使用 findAny 提高性能。
// 产品类
class Product {
    private String name;
    private int stock;
    private int minStock;
    
    // 构造函数、getter和setter省略
    
    public Product(String name, int stock, int minStock) {
        this.name = name;
        this.stock = stock;
        this.minStock = minStock;
    }
    
    public String getName() {
        return name;
    }
    
    public int getStock() {
        return stock;
    }
    
    public int getMinStock() {
        return minStock;
    }
}

// 创建大量产品数据
List<Product> products = new ArrayList<>();
for (int i = 1; i <= 10000; i++) {
    // 大部分产品库存充足,只有少数库存不足
    int stock = (i % 100 == 0) ? 5 : 50;
    products.add(new Product("产品" + i, stock, 10));
}

// 并行查找任意一个库存不足的产品
long startTime = System.currentTimeMillis();
Optional<Product> anyLowStockProduct = products.parallelStream()
                                             .filter(product -> product.getStock() < product.getMinStock())  // 过滤库存不足的产品
                                             .findAny();  // 查找任意一个符合条件的元素
long endTime = System.currentTimeMillis();

// 处理查找结果
anyLowStockProduct.ifPresent(product -> {
    System.out.println("发现库存不足的产品: " + product.getName());
    System.out.println("当前库存: " + product.getStock() + ", 最低库存要求: " + product.getMinStock());
    // 在实际应用中,这里可能会调用一个方法来生成补货提醒
    // generateRestockAlert(product);
});

System.out.println("查找耗时: " + (endTime - startTime) + "毫秒");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  1. 获取样本数据:从大数据集中获取一个样本进行分析。
// 日志类
class LogEntry {
    private String message;
    private LogLevel level;
    
    // 构造函数、getter和setter省略
    
    public LogEntry(String message, LogLevel level) {
        this.message = message;
        this.level = level;
    }
    
    public String getMessage() {
        return message;
    }
    
    public LogLevel getLevel() {
        return level;
    }
}

enum LogLevel {
    INFO, WARNING, ERROR
}

// 日志列表
List<LogEntry> logs = Arrays.asList(
    new LogEntry("系统启动", LogLevel.INFO),
    new LogEntry("用户登录", LogLevel.INFO),
    new LogEntry("数据库连接失败", LogLevel.ERROR),
    new LogEntry("文件不存在", LogLevel.WARNING),
    new LogEntry("内存不足", LogLevel.ERROR)
);

// 获取一个错误日志样本
Optional<LogEntry> errorLogSample = logs.stream()
                                      .filter(log -> log.getLevel() == LogLevel.ERROR)  // 过滤错误日志
                                      .findAny();  // 获取任意一个错误日志

// 分析样本日志
errorLogSample.ifPresent(log -> {
    System.out.println("错误日志样本: " + log.getMessage());
    // 在实际应用中,这里可能会调用一个方法来分析错误模式
    // analyzeErrorPattern(log);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  1. 与其他操作组合:结合其他流操作实现复杂功能。
// 学生类
class Student {
    private String name;
    private int score;
    private double attendanceRate;
    
    // 构造函数、getter和setter省略
    
    public Student(String name, int score, double attendanceRate) {
        this.name = name;
        this.score = score;
        this.attendanceRate = attendanceRate;
    }
    
    public String getName() {
        return name;
    }
    
    public int getScore() {
        return score;
    }
    
    public double getAttendanceRate() {
        return attendanceRate;
    }
}

// 学生列表
List<Student> students = Arrays.asList(
    new Student("张三", 85, 0.9),
    new Student("李四", 92, 0.95),
    new Student("王五", 78, 0.85),
    new Student("赵六", 95, 0.98),
    new Student("钱七", 88, 0.92)
);

// 查找第一个成绩优秀且出勤率高的学生
Optional<Student> excellentStudent = students.stream()
                                           .filter(student -> student.getScore() > 90)  // 过滤成绩优秀的学生
                                           .filter(student -> student.getAttendanceRate() > 0.9)  // 过滤出勤率高的学生
                                           .findFirst();  // 查找第一个符合条件的学生

// 处理查找结果
excellentStudent.ifPresent(student -> {
    System.out.println("优秀学生: " + student.getName());
    System.out.println("成绩: " + student.getScore() + ", 出勤率: " + student.getAttendanceRate());
    // 在实际应用中,这里可能会调用一个方法来颁发奖学金
    // awardScholarship(student);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
  1. 使用 Optional 的高级特性:利用 Optional 提供的方法优雅地处理结果。
// 查找第一个成绩优秀的学生,如果没有则创建一个默认学生
Student defaultStudent = new Student("默认学生", 60, 0.8);
Student excellentOrDefault = students.stream()
                                   .filter(student -> student.getScore() > 90)  // 过滤成绩优秀的学生
                                   .findFirst()  // 查找第一个符合条件的学生
                                   .orElse(defaultStudent);  // 如果没有找到,则返回默认学生

System.out.println("优秀学生或默认学生: " + excellentOrDefault.getName());

// 使用orElseGet延迟创建默认值
Student lazyDefault = students.stream()
                             .filter(student -> student.getScore() > 99)  // 过滤成绩特别优秀的学生
                             .findFirst()  // 查找第一个符合条件的学生
                             .orElseGet(() -> {
                                 System.out.println("没有找到特别优秀的学生,创建默认学生");
                                 return new Student("默认特优生", 100, 1.0);
                             });  // 如果没有找到,则创建一个默认学生

System.out.println("特别优秀学生或默认学生: " + lazyDefault.getName());

// 使用map转换结果
String excellentStudentName = students.stream()
                                     .filter(student -> student.getScore() > 90)  // 过滤成绩优秀的学生
                                     .findFirst()  // 查找第一个符合条件的学生
                                     .map(Student::getName)  // 提取学生姓名
                                     .orElse("未找到");  // 如果没有找到,则返回"未找到"

System.out.println("优秀学生姓名: " + excellentStudentName);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

通过 findFirst 和 findAny 操作,我们可以轻松地在流中查找元素,这比传统的使用循环和条件判断的方式更加简洁、可读性更强,并且可以与其他流操作无缝组合,构建强大的数据处理管道。特别是在需要快速找到满足特定条件的元素时,这些操作能够显著简化代码并提高可维护性。

这两个方法都返回 Optional 对象,可以优雅地处理可能为空的情况,避免空指针异常。Optional 提供了丰富的方法,如 isPresent、ifPresent、orElse、orElseGet、map 等,可以灵活地处理查找结果。

在并行处理大数据集时,findAny 通常比 findFirst 更高效,因为它不需要协调各个分片以确定哪个元素是第一个。如果不关心返回的是哪个元素,只需要找到任意一个满足条件的元素,建议使用 findAny,特别是在并行流中。

编辑此页 (opens new window)
上次更新: 2025/03/16, 13:51:03
构造器和数组的引用
并行流与串行流

← 构造器和数组的引用 并行流与串行流→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式