当前位置: 首页 > news >正文

Java Stream 流:让数据处理更优雅的 “魔法管道“

作为一名 Java 开发者,我还记得刚接触 Stream 流时的惊喜 —— 原来数据处理可以如此简洁优雅。对于刚学习 Java 的大学生来说,Stream 流可能看起来有点抽象,但一旦掌握,它会成为你代码中的 "瑞士军刀"。今天我们就来聊聊 Stream 流到底是什么,以及它能帮我们解决什么问题。

一、为什么需要 Stream 流?

在没有 Stream 流的年代,我们处理集合数据通常是这样的:

// 传统方式:筛选出年龄大于18的学生并打印名字
List<Student> students = ...;
for (Student s : students) {if (s.getAge() > 18) {System.out.println(s.getName());}
}

这种代码没什么问题,但当需求变得复杂时(比如多步筛选、转换、聚合),代码会变得冗长且可读性差。Stream 流的出现就是为了:

  1. 简化集合数据的处理逻辑
  2. 让代码更易读、更易维护
  3. 支持函数式编程风格
  4. 轻松实现并行处理(无需手动处理多线程)

二、什么是 Stream 流?

简单来说,Stream 流是 Java 8 引入的一套处理集合数据的 API,它像一个 "数据管道":

  • 数据源(通常是集合)的数据流入管道
  • 经过一系列中间操作(筛选、转换、排序等)
  • 最终通过终止操作得到结果

注意:Stream 流不会改变原始数据源,所有操作都是惰性执行的(直到终止操作才会真正执行)。

三、Stream 流的常用操作

在学习具体方法前,先明确一个重要概念:Stream 的操作分为中间操作和终止操作。

  • 中间操作:返回新的 Stream,支持链式调用(惰性执行,只有终止操作触发时才会执行)
  • 终止操作:返回具体结果(如集合、数值、布尔值等),触发整个流的计算

记住这个分类,能帮你理解 Stream 的执行逻辑。

        假设我们有一个学生列表,包含姓名、年龄、成绩等信息:

class Student {private String name;private int age;private double score;// 构造方法、getter、toString省略
}List<Student> students = Arrays.asList(new Student("张三", 20, 85.5),new Student("李四", 17, 92.0),new Student("王五", 22, 78.5),new Student("赵六", 19, 90.0)
);

(一)、常用中间操作(返回 Stream)

1. 筛选与切片

方法作用示例
filter(Predicate<T> p)保留满足条件的元素stream.filter(s -> s.getAge() > 18)
limit(long n)截取前 n 个元素stream.limit(3)(取前 3 个)
skip(long n)跳过前 n 个元素stream.skip(2)(跳过前 2 个)
distinct()去重(依赖 equals ())stream.distinct()

实战场景:从学生列表中获取年龄 > 18 的前 2 名(排除重复数据)

List<Student> result = students.stream().filter(s -> s.getAge() > 18).distinct().limit(2).collect(Collectors.toList());

2. 映射(类型转换)

方法作用示例
map(Function<T, R> f)将 T 类型转换为 R 类型stream.map(Student::getName)(转姓名)
mapToInt(ToIntFunction<T> f)转换为 IntStreamstream.mapToInt(Student::getAge)
mapToLong(ToLongFunction<T> f)转换为 LongStreamstream.mapToLong(User::getId)
mapToDouble(ToDoubleFunction<T> f)转换为 DoubleStreamstream.mapToDouble(Student::getScore)
flatMap(Function<T, Stream<R>> f)将流中每个元素转换为子流并合并见下方示例

flatMap 示例:将列表中的字符串拆分为单个字符(如 ["abc", "def"] → ['a','b','c','d','e','f'])

List<String> words = Arrays.asList("abc", "def");
List<Character> chars = words.stream().flatMap(s -> {List<Character> list = new ArrayList<>();for (char c : s.toCharArray()) {list.add(c);}return list.stream(); // 每个字符串转成字符流}).collect(Collectors.toList());

3. 排序

方法作用示例
sorted()自然排序(需元素实现 Comparable)stream.sorted()(如 String、Integer 的默认排序)
sorted(Comparator<T> c)自定义排序stream.sorted(Comparator.comparingInt(Student::getAge))

示例:按成绩降序排序,成绩相同按年龄升序

students.stream().sorted(Comparator.comparingDouble(Student::getScore).reversed() // 成绩降序.thenComparingInt(Student::getAge)) // 年龄升序.forEach(System.out::println);

4. 其他中间操作

方法作用示例
peek(Consumer<T> c)对流中元素执行操作(不改变元素),常用于调试stream.peek(s -> System.out.println("处理中:" + s))

(二)、常用终止操作(返回具体结果)

1. 遍历与收集

方法作用示例
forEach(Consumer<T> c)遍历元素stream.forEach(System.out::println)
collect(Collector c)收集流为集合 / 对象见下方详细说明

collect 常用收集器(需导入java.util.stream.Collectors):

  • toList():转为 List → collect(Collectors.toList())
  • toSet():转为 Set → collect(Collectors.toSet())
  • toMap():转为 Map(需指定 key 和 value) → collect(Collectors.toMap(Student::getName, Student::getScore))
  • joining():字符串拼接(适用于 String 流) → stream.map(Student::getName).collect(Collectors.joining(","))(用逗号分隔)

2. 聚合计算(适用于数值流或对象流)

方法作用示例
count()统计元素数量long count = stream.count()
max(Comparator<T> c)求最大值stream.max(Comparator.comparingInt(Student::getAge))
min(Comparator<T> c)求最小值stream.min(Comparator.comparingDouble(Student::getScore))
sum()(数值流)求和stream.mapToInt(Student::getAge).sum()
average()(数值流)求平均值stream.mapToDouble(Student::getScore).average()

示例:求 18 岁以上学生的最高成绩

Optional<Double> maxScore = students.stream().filter(s -> s.getAge() > 18).map(Student::getScore).max(Double::compare);// Optional处理(避免空指针)
double score = maxScore.orElse(0.0);

3. 匹配判断

方法作用示例
allMatch(Predicate<T> p)所有元素是否满足条件stream.allMatch(s -> s.getAge() > 18)(是否全是成年人)
anyMatch(Predicate<T> p)是否有元素满足条件stream.anyMatch(s -> s.getScore() == 100)(是否有满分)
noneMatch(Predicate<T> p)所有元素是否都不满足条件stream.noneMatch(s -> s.getName().isEmpty())(是否没有空姓名)

示例:检查是否所有学生都及格(假设 60 分为及格线)

boolean allPass = students.stream().allMatch(s -> s.getScore() >= 60);

4. 查找元素

方法作用示例
findFirst()返回第一个元素stream.findFirst()
findAny()返回任意一个元素(并行流中效率高)stream.findAny()

示例:获取第一个成年人学生

Optional<Student> firstAdult = students.stream().filter(s -> s.getAge() > 18).findFirst();

5. 规约(复杂聚合)

方法作用示例
reduce(T identity, BinaryOperator<T> accumulator)从初始值开始,将元素累加计算见下方示例

示例:计算所有学生的年龄总和(用 reduce 实现)

int totalAge = students.stream().mapToInt(Student::getAge).reduce(0, (a, b) -> a + b); // 0是初始值,a是累加结果,b是当前元素

(三)、并行流相关

当数据量很大时,我们可以轻松将串行流转换为并行流

并行流会自动利用多核处理器进行计算,无需我们手动创建线程池,这对于大数据处理非常有用。

方法作用示例
parallel()将串行流转为并行流stream.parallel()
sequential()将并行流转为串行流stream.sequential()

示例:大数据量下并行计算平均成绩

// 数据量越大,并行流优势越明显
double avg = students.parallelStream() // 直接获取并行流.mapToDouble(Student::getScore).average().orElse(0.0);

四、初学者常见问题

  1. Stream 只能用一次:一个 Stream 执行终止操作后就会关闭,再次使用会报错

    Stream<Student> stream = students.stream();
    stream.forEach(System.out::println); // 终止操作,流关闭
    stream.count(); // 报错:java.lang.IllegalStateException: stream has already been operated upon or closed
    
  2. 区分 map 和 flatMap:map 是 "一对一" 转换,flatMap 是 "一对多" 并合并流(如将列表中的列表展开)

  3. Optional 的使用:max ()、findFirst () 等方法返回 Optional,需用 orElse ()、ifPresent () 等方法处理,避免空指针

五、总结

Stream 流的方法虽然多,但核心是围绕 "数据管道" 的思想设计的。记住:先通过中间操作构建处理流程,再用终止操作获取结果

http://www.dtcms.com/a/528653.html

相关文章:

  • 查看网站服务器ip受欢迎的购物网站建设
  • fpga实现音频预加重(pre-emphasis)滤波器
  • Rust中的Enum与Struct详解
  • C语言进阶知识--自定义类型:结构体
  • OptionMaster Pro:期权数据智能处理系统的设计与实现
  • C. Maximum GCD on Whiteboard
  • 【AI论文】DITING:网络小说翻译评估的多智能体基准测试框架
  • 吉林省软环境建设网站免费开网站系统
  • vulnerable_docker_containement 靶机
  • Docker方式安装Nginx
  • 标签噪声学习:理论与方法详解
  • Docker 部署 Debian 全流程教程
  • 上海做网站公司wordpress 活动网站
  • Bee:从 0 到 1 打造一套现代化的全栈后台管理系统(React + Spring Boot)
  • 计算机操作系统:“抖动”与工作集
  • 数据结构(长期更新)第4讲:单链表
  • C#测试调用OpenXml填充word文档的表格
  • 基于python的网站开发项目做外汇网站代理商
  • 对TCP/IP协议的理解
  • 如何判断“IP+端口“通不通
  • tensorrt c++部署
  • TypeScript 基础类型与接口详解
  • MySQL————mysql connect
  • 能打开各种网站的搜索引擎原神网页设计作业
  • 【SpringCloud】Ribbon(LoadBalancer ) 和 Feign
  • Dockerfile 中 ENTRYPOINT 和 CMD 有什么区别 ?
  • 网站数据库模版深圳网站建设黄浦网络 骗钱
  • vs code 下docker使用方法,以php 项目为示例
  • 番禺网站建设哪里好深圳十大传媒公司
  • 前端常见的设计模式