Java 8 Stream API学习总结

简介: Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

这一次为什么要系统性的总结一下 Java 8 Stream API 呢?说得简单点,我们先不论性能,我们就是为了 装x ,而且要让这个 x 装得再优秀一些,仅此而已!

Stream Tests

Stream基础知识

流程

创建流流的中间操作流的最终操作

创建流

我们需要把哪些元素放入流中,常见的api有:

// 使用List创建流
list.stream()

// 使用一个或多个元素创建流
Stream.of(T value)
Stream.of(T... values)

// 使用数组创建流
Arrays.stream(T[] array)

// 创建一个空流
Stream.empty()

// 两个流合并
Stream.concat(Stream<? extends T> a, Stream<? extends T> b)

// 无序无限流
Stream.generate(Supplier<T> s)

// 通过迭代产生无限流
Stream.iterate(final T seed, final UnaryOperator<T> f)

流的中间操作

// 元素过滤
filter
limit
skip
distinct

// 映射
map
flatmap

// 排序

流的最终操作

通过流对元素的最终操作,我们想得到一个什么样的结果

构造测试数据

员工实体类

/**
 * 员工实体类
 * @author Erwin Feng
 * @since 2020/4/27 2:10
 */
public class Employee {

    /** 员工ID */
    private Integer id;

    /** 员工姓名 */
    private String name;

    /** 员工薪资 */
    private Double salary;
    
    /** 构造方法、getter and setter、toString */
}

测试数据列表

[
    {
        "id":1,
        "name":"Jacob",
        "salary":1000
    },
    {
        "id":2,
        "name":"Sophia",
        "salary":2000
    },
    {
        "id":3,
        "name":"Rose",
        "salary":3000
    },
    {
        "id":4,
        "name":"Lily",
        "salary":4000
    },
    {
        "id":5,
        "name":"Daisy",
        "salary":5000
    },
    {
        "id":6,
        "name":"Jane",
        "salary":5000
    },
    {
        "id":7,
        "name":"Jasmine",
        "salary":6000
    },
    {
        "id":8,
        "name":"Jack",
        "salary":6000
    },
    {
        "id":9,
        "name":"Poppy",
        "salary":7000
    }
]

Stream API Test

filter 过滤

需求:查找薪酬为5000的员工列表

List<Employee> employees = list.stream().filter(employee -> employee.getSalary() == 5000)
        .peek(System.out::println)
        .collect(Collectors.toList());
Assert.assertEquals(2, employees.size());

map 映射

需求:将薪酬大于5000的员工放到Leader对象中

List<Leader> leaders = list.stream().filter(employee -> employee.getSalary() > 5000).map(employee -> {
    Leader leader = new Leader();
    leader.setName(employee.getName());
    leader.setSalary(employee.getSalary());
    return leader;
}).peek(System.out::println).collect(Collectors.toList());
Assert.assertEquals(3, leaders.size());

flatMap 水平映射

需求:将多维的列表转化为单维的列表

说明:

我们将薪酬在1000-3000的分为一个列表,4000-5000分为一个列表,6000-7000分为一个列表。

将这三个列表组合在一起形成一个多维列表。

List<Employee> employees = multidimensionalList.stream().flatMap(Collection::stream).collect(Collectors.toList());
Assert.assertEquals(9, employees.size());

sorted 排序

需求:根据薪酬排序

// 薪酬从小到大排序
List<Employee> employees = list.stream().sorted(Comparator.comparing(Employee::getSalary)).peek(System.out::println).collect(Collectors.toList());

// 薪酬从大到小排序
List<Employee> employees2 = list.stream().sorted(Comparator.comparing(Employee::getSalary).reversed()).peek(System.out::println).collect(Collectors.toList());

min 最小值

double minValue = list.stream().mapToDouble(Employee::getSalary).min().orElse(0);
Assert.assertEquals(1000, minValue, 0.0);

Employee employee = list.stream().min(Comparator.comparing(Employee::getSalary)).orElse(null);
assert employee != null;
Assert.assertEquals(employee.getSalary(), minValue, 0.0);

max 最大值

double maxValue = list.stream().mapToDouble(Employee::getSalary).max().orElse(0);
Assert.assertEquals(7000, maxValue, 0.0);

average 平均值

double sum = list.stream().mapToDouble(Employee::getSalary).sum();
double averageValue = list.stream().mapToDouble(Employee::getSalary).average().orElse(0);
Assert.assertEquals(sum / list.size(), averageValue, 0.0);

match 匹配

// allMatch 集合中的元素都要满足条件才会返回true
// 薪酬都是大于等于1000的
boolean isAllMatch = list.stream().allMatch(employee -> employee.getSalary() >= 1000);
Assert.assertTrue(isAllMatch);

// anyMatch 集合中只要有一个元素满足条件就会返回true
// 有没有薪酬大于等于7000
boolean isAnyMatch = list.stream().anyMatch(employee -> employee.getSalary() >= 7000);
Assert.assertTrue(isAnyMatch);

// noneMatch 集合中没有元素满足条件才会返回true
// 没有薪酬小于1000的
boolean isNoneMatch = list.stream().noneMatch(employee -> employee.getSalary() < 1000);
Assert.assertTrue(isNoneMatch);

distinct 去重

默认的 distinct() 不接收参数,是根据 Object#equals(Object) 去重。根据API介绍,这是一个有中间状态的操作。

List<Employee> employees = list.stream().distinct().collect(Collectors.toList());
Assert.assertEquals(9, employees.size());

如果我们要根据对象中的某个属性去重的,可以使用 StreamEx

// 使用StreamEx去重
List<Employee> employees2 = StreamEx.of(list).distinct(Employee::getSalary).collect(Collectors.toList());
Assert.assertEquals(7, employees2.size());

当然也可以使用JDK Stream API

private static <T>Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Map<Object, Boolean> result = new ConcurrentHashMap<>();
    return t -> result.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}

List<Employee> employees3 = list.stream().filter(distinctByKey(Employee::getSalary)).collect(Collectors.toList());
Assert.assertEquals(7, employees3.size());

reduce 裁减

需求:计算薪酬总和

// 先将员工列表转换为薪酬列表
// 再计算薪酬总和
double salarySum = list.stream().map(Employee::getSalary).reduce(Double::sum).orElse(0.0);
double sum = list.stream().mapToDouble(Employee::getSalary).sum();
Assert.assertEquals(salarySum, sum, 0.0);

另外,我们也可以设定一个累加函数的标识值

double salarySum5 = list.stream().map(Employee::getSalary).reduce(1.00, Double::sum);
Assert.assertEquals(salarySum5, sum + 1, 0.0);

collector 流的终止结果

// joining 拼接字符串
String employeeNames = list.stream().map(Employee::getName).collect(Collectors.joining(", "));
System.out.println(employeeNames); // Jacob, Sophia, Rose, Lily, Daisy, Jane, Jasmine, Jack, Poppy

// 返回一个List
List<String> employeeNameList = list.stream().map(Employee::getName).collect(Collectors.toList());
System.out.println(employeeNameList);

// 返回一个Set
Set<String> employeeNameSet = list.stream().map(Employee::getName).collect(Collectors.toSet());
System.out.println(employeeNameSet);

// 返回一个Vector
Vector<String> employeeNameVector = list.stream().map(Employee::getName).collect(Collectors.toCollection(Vector::new));
System.out.println(employeeNameVector);

// 返回一个Map
Map<Integer, String> employeesMap = list.stream().collect(Collectors.toMap(Employee::getId, Employee::getName));
System.out.println(employeesMap);

count 统计

需求:薪酬为5000的员工数

不使用流

int count2 = 0;
for (Employee employee : list) {
    if (employee.getSalary() == 5000) {
        count2++;
    }
}
System.out.println(count2);

使用流

long count3 = list.stream().filter(employee -> employee.getSalary() == 5000).count();
Assert.assertEquals(count3, count2);

summarizingDouble 统计分析

DoubleSummaryStatistics employeeSalaryStatistics = list.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println("employee salary statistics:" + employeeSalaryStatistics);

DoubleSummaryStatistics employeeSalaryStatistics2 = list.stream().mapToDouble(Employee::getSalary).summaryStatistics();
System.out.println("employee salary statistics2:" + employeeSalaryStatistics2);

{count=9, sum=39000.000000, min=1000.000000, average=4333.333333, max=7000.000000}

partitioningBy 分区

分成满足条件(true)和不满足条件(false)两个区

需求:找出薪酬大于5000的员工

Map<Boolean, List<Employee>> map = list.stream().collect(Collectors.partitioningBy(employee -> employee.getSalary() > 5000));
System.out.println("true:" + map.get(Boolean.TRUE));
System.out.println("false:" + map.get(Boolean.FALSE));

true:[Employee{id=7, name='Jasmine', salary=6000.0}, Employee{id=8, name='Jack', salary=6000.0}, Employee{id=9, name='Poppy', salary=7000.0}]

false:[Employee{id=1, name='Jacob', salary=1000.0}, Employee{id=2, name='Sophia', salary=2000.0}, Employee{id=3, name='Rose', salary=3000.0}, Employee{id=4, name='Lily', salary=4000.0}, Employee{id=5, name='Daisy', salary=5000.0}, Employee{id=6, name='Jane', salary=5000.0}]

groupingBy 分组

需求:根据员工薪酬分组

Map<Double, List<Employee>> map = list.stream().collect(Collectors.groupingBy(Employee::getSalary));
System.out.println(map);

再举一个例子:薪酬 一> 总和(薪酬*员工数)

Map<Double, Double> map3 = list.stream().collect(Collectors.groupingBy(Employee::getSalary, Collectors.summingDouble(Employee::getSalary)));
System.out.println(map3);

parallel 平行计算

简单的说,就是启动多个线程计算

private static void cal(Employee employee) {
    try {
        long sleepTime = employee.getSalary().longValue();
        TimeUnit.MILLISECONDS.sleep(sleepTime);
        logger.info("employee name: {}", employee.getName());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

list.stream().parallel().forEach(StreamTest::cal);
2020-05-15 01:47:14.231 [ForkJoinPool.commonPool-worker-4] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Jacob
2020-05-15 01:47:15.226 [ForkJoinPool.commonPool-worker-2] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Sophia
2020-05-15 01:47:16.226 [ForkJoinPool.commonPool-worker-1] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Rose
2020-05-15 01:47:17.226 [ForkJoinPool.commonPool-worker-3] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Lily
2020-05-15 01:47:18.225 [main] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Jane
2020-05-15 01:47:18.228 [ForkJoinPool.commonPool-worker-7] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Daisy
2020-05-15 01:47:19.226 [ForkJoinPool.commonPool-worker-5] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Jack
2020-05-15 01:47:19.228 [ForkJoinPool.commonPool-worker-6] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Jasmine
2020-05-15 01:47:21.234 [ForkJoinPool.commonPool-worker-4] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Poppy

file 文件操作

try (PrintWriter printWriter = new PrintWriter(Files.newBufferedWriter(Paths.get(tempFilePath)))) { // 使用 try 自动关闭流
    list.forEach(printWriter::println);
    list.forEach(employee -> printWriter.println(employee.getName())); // 将员工的姓名写到文件中
}

// 从文件中读取员工的姓名
List<String> s = Files.lines(Paths.get(tempFilePath)).peek(System.out::println).collect(Collectors.toList());

测试代码

Study Java 8 Stream API

StreamTest Method List

学习链接

目录
相关文章
|
21天前
|
Oracle Java 关系型数据库
java 入门学习视频_2025 最新 java 入门零基础学习视频教程
《Java 21 入门实操指南(2025年版)》提供了Java最新特性的开发指导。首先介绍了JDK 21和IntelliJ IDEA 2025.1的环境配置,包括环境变量设置和预览功能启用。重点讲解了Java 21三大核心特性:虚拟线程简化高并发编程,Record模式优化数据解构,字符串模板提升字符串拼接可读性。最后通过图书管理系统案例,展示如何运用Record定义实体类、使用Stream API进行数据操作,以及结合字符串模板实现控制台交互。该指南完整呈现了从环境搭建到实际项目开发的Java 21全流程实
48 1
|
24天前
|
Java
银行转账p图软件,对公转账截图生成器,java版开发银行模拟器【仅供学习参考】
这是一套简单的银行账户管理系统代码,包含`BankAccount`和`BankSystem`两个核心类。`BankAccount`负责单个账户的管理
|
24天前
|
存储 Java 数据库
银行流水生成器在线制作,银行转账p图在线生成,java实现最牛的生成器【仅供学习用途】
本示例展示了一个基于Java的银行交易记录管理系统基础架构,涵盖交易记录生成、数字签名加密及账本存储功能。核心内容包括:1) TransactionRecord类
|
24天前
|
Java 数据库 数据安全/隐私保护
银行流水生成器在线制作,银行转账p图在线生成,java实现最牛的生成器【仅供学习用途】
本资料探讨银行系统核心技术,涵盖交易记录生成、电子回单加密验真及基于Java的财务管理系统开发。主要内容包括:交易记录实体类设计(不可变性与数字签名)
|
24天前
|
数据采集 搜索推荐 算法
Java 大视界 -- Java 大数据在智能教育学习社区用户互动分析与社区活跃度提升中的应用(274)
本文系统阐述 Java 大数据技术在智能教育学习社区中的深度应用,涵盖数据采集架构、核心分析算法、活跃度提升策略及前沿技术探索,为教育数字化转型提供完整技术解决方案。
|
25天前
|
算法 Java 测试技术
Java 从入门到实战完整学习路径与项目实战指南
本文详细介绍了“Java从入门到实战”的学习路径与应用实例,涵盖基础、进阶、框架工具及项目实战四个阶段。内容包括环境搭建、语法基础、面向对象编程,数据结构与算法、多线程并发、JVM原理,以及Spring框架等核心技术。通过学生管理系统、文件下载器和博客系统等实例,帮助读者将理论应用于实践。最后,提供全链路电商系统的开发方案,涉及前后端技术栈与分布式架构。附代码资源链接,助力成为合格的Java开发者。
51 4
|
1月前
|
存储 安全 Java
Java 基础知识超详细整理总结及学习要点解析
本文全面总结了Java基础知识,涵盖语言特性、语法基础、面向对象编程、集合框架、异常处理等核心内容。文章详细解析了Java的面向对象特性(如类与对象、构造方法、方法重载)、集合框架(如ArrayList、HashMap)、异常分类及处理,并深入探讨JVM内存模型、字符串比较、BigDecimal使用等重要知识点。此外,还提供了实际应用示例,帮助开发者更好地理解和掌握Java编程。代码资源可从文末链接获取。
319 4
|
1月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
89 5
|
1月前
|
Cloud Native Java 程序员
【2025 最新版互联网一线大厂 Java 程序员面试 + 学习指南】覆盖全面面试知识点、实用面试技巧及前沿技术实操内容
本内容涵盖互联网大厂主流技术栈的最新实操指南,包括微服务架构(Spring Cloud Alibaba Nacos、OpenFeign、Spring Cloud Gateway)、容器化与Kubernetes、云原生技术(Istio、Prometheus+Grafana)、高性能开发(Reactor响应式编程、CompletableFuture异步编程)及数据持久化(Redis分布式锁、ShardingSphere分库分表)。通过详细代码示例和操作步骤,帮助开发者掌握核心技术,适用于本地环境搭建与模块功能实践。适合Java程序员学习和面试准备,附带资源链接供深入研究。
59 5
|
1月前
|
缓存 算法 Java
【Java 程序员面试 + 学习指南】覆盖互联网一线大厂 Java 程序员所需面试知识点与技巧
本指南专为Java程序员准备互联网大厂面试而设,涵盖面试知识点与技巧两大部分。知识点包括Java基础(面向对象、集合框架、并发编程)、JVM(内存分区、回收机制、类加载机制)、数据库(MySQL、Redis)、开发框架(Spring、Spring Boot、MyBatis)及其他相关技术(计算机网络、操作系统)。面试技巧涉及简历撰写、项目经验阐述及答题策略,助你全面提升面试成功率。提供资源链接,支持深入学习。
49 4