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

Java8函数式编程之Stream API

今天,我们来全面、深入地介绍一下 Java 8 函数式编程中最具革命性的特性—— Stream API

Stream API 是 Java 中处理集合(Collection)数据的重大升级。它允许你以声明式(Declarative)、类似于 SQL 语句的风格来操作数据,此外,它还能非常方便地利用多核架构进行并行处理,而无需编写复杂的多线程代码。


1. 核心思想:它是什么?不是什么?

什么是 Stream?
  • 它不是数据结构:它不存储数据,而是对数据源(通常是集合)进行计算操作的流水线
  • 它是高级迭代器:它提供了丰富的操作(过滤、映射、排序、归约等),让你可以透明地并行处理数据,而无需自己写 forwhile 循环。
  • ** functional in nature**:它对源数据进行的操作不会修改源数据本身(它会产生一个新的 Stream),并且操作通常是无状态无副作用的。
Stream vs. Collection
  • Collection 主要关心的是数据的存储高效访问(如 ArrayList, HashSet)。
  • Stream 主要关心的是数据的计算处理(如过滤、查找、转换、聚合)。

一个生动的比喻
集合就像是一个存储了未加工原料的仓库(比如一箱生土豆)。
Stream 就像是一条土豆加工流水线。这条流水线会依次进行一系列操作:将土豆从箱中取出(获取流)-> 清洗土豆(过滤 filter)-> 削皮(转换 map)-> 切成薯条(转换 map)-> 按尺寸分拣(排序 sorted)-> 打包(终结操作 collect)。流水线本身不存储土豆,它只是对土豆进行处理。


2. Stream 操作的三个步骤

使用 Stream 通常需要三个步骤,形成一个操作链(流水线):

  1. 创建 Stream:从一个数据源(如集合、数组、I/O通道)获取一个流。
  2. 中间操作:在一个或多个中间操作中,对 Stream 进行一系列处理(如过滤、映射),这些操作返回一个新的 Stream,因此可以链式调用。重要:中间操作是惰性的,在终结操作被调用前,它们不会真正执行。
  3. 终结操作:执行整个流水线并产生结果。执行后,该流就被消费掉了,不能再使用。结果可以是一个新集合、一个值、或者什么都不返回(如 forEach)。

3. 创建 Stream

// 1. 从集合创建 (最常用)
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();          // 顺序流
Stream<String> parallelStream = list.parallelStream(); // 并行流// 2. 从数组创建
String[] array = {"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(array);// 3. 使用 Stream.of() 静态方法
Stream<String> stream3 = Stream.of("a", "b", "c");// 4. 创建无限流 (通常与 limit() 结合使用)
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 2); // 无限偶数流
Stream<Double> randomStream = Stream.generate(Math::random);   // 无限随机数流

4. 中间操作

中间操作是构建流水线的核心,它们会返回一个新的 Stream。

操作方法签名描述示例
过滤Stream<T> filter(Predicate<T>)排除不满足条件的元素stream.filter(s -> s.startsWith("A"))
映射<R> Stream<R> map(Function<T, R>)将元素转换为其他形式或提取信息stream.map(String::toUpperCase)
stream.map(s -> s.length())
去重Stream<T> distinct()通过 hashCode()equals() 去重stream.distinct()
排序Stream<T> sorted()
Stream<T> sorted(Comparator<T>)
产生一个按自然顺序或比较器排序的新流stream.sorted()
stream.sorted(Comparator.reverseOrder())
截取Stream<T> limit(long maxSize)使其元素不超过给定数量stream.limit(10)
跳过Stream<T> skip(long n)跳过前N个元素stream.skip(5)
** peek **Stream<T> peek(Consumer<T>)主要用于调试,查看流经流的元素stream.peek(System.out::println)

5. 终结操作

终结操作会触发流水线的实际执行。

操作方法签名描述示例
遍历void forEach(Consumer<T>)对流中每个元素执行操作stream.forEach(System.out::println)
收集<R, A> R collect(Collector<T, A, R>)将流转换为其他形式(如List, Set, Map)stream.collect(Collectors.toList())
匹配boolean allMatch(Predicate<T>)
boolean anyMatch(Predicate<T>)
boolean noneMatch(Predicate<T>)
检查流中元素是否全部/任一/没有一个匹配断言stream.allMatch(s -> s.length() > 3)
计数long count()返回流中元素个数stream.count()
查找Optional<T> findFirst()
Optional<T> findAny()
返回第一个/任意一个元素(在并行流中 findAny 效率更高)stream.findFirst()
归约Optional<T> reduce(BinaryOperator<T>)
T reduce(T identity, BinaryOperator<T>)
将流中元素反复结合,得到一个值(如求和、求最大最小值)stream.reduce((a, b) -> a + b)
stream.reduce(0, (a, b) -> a + b)

6. 综合示例:把一切结合起来

假设我们有一个 Person 对象的列表,我们要进行一系列复杂查询。

// 数据源
List<Person> people = Arrays.asList(new Person("Alice", 25, "London"),new Person("Bob", 30, "New York"),new Person("Charlie", 20, "London"),new Person("Diana", 25, "Paris")
);// 目标:找出所有来自 London 的人,提取他们的名字,并按字母排序,最后放入一个新列表。
List<String> namesOfLondoners = people.stream()       // 1. 获取流.filter(p -> "London".equals(p.getCity()))   // 2. 过滤:只保留 London 的人.map(Person::getName)                        // 3. 映射:将 Person 对象映射为其名字 (String).sorted()                                    // 4. 排序:按字母顺序排序.collect(Collectors.toList());               // 5. 收集:将结果转换为 ListSystem.out.println(namesOfLondoners); // 输出: [Alice, Charlie]// 更多例子:
// - 计算来自 London 的平均年龄
double averageAge = people.stream().filter(p -> "London".equals(p.getCity())).mapToInt(Person::getAge) // 专为 int 设计的流,有 sum(), average() 等便捷方法.average().orElse(0.0); // 如果没有任何元素,防止 NoSuchElementException// - 按城市对人进行分组
Map<String, List<Person>> peopleByCity = people.stream().collect(Collectors.groupingBy(Person::getCity));
// 结果: {New York=[Bob], London=[Alice, Charlie], Paris=[Diana]}// - 判断是否所有人都大于18岁
boolean allAdult = people.stream().allMatch(p -> p.getAge() > 18);

7. 并行流

将顺序流转换为并行流非常简单,通常只需将 .stream() 替换为 .parallelStream(),或者在流中间调用 .parallel() 方法。

// 顺序流
long count = people.stream().filter(p -> p.getAge() > 20).count();// 并行流
long count = people.parallelStream().filter(p -> p.getAge() > 20).count();

原理:Stream API 在内部使用 Fork/Join 框架将任务拆分到多个线程上执行,最后将结果合并。

注意事项

  • 并非总是更快:线程的创建、管理和同步本身就有开销。对于小数据量,顺序流往往更快。
  • 状态问题:确保传递给流操作(如 filter, map)的函数是无状态无干扰的(不修改外部数据源),否则并行计算会产生竞态条件,导致错误结果。
  • 适用场景:大数据集、处理耗时长的操作(如IO),且任务可独立并行执行时,性能提升最明显。

总结

Stream API 的核心优势:

  1. 声明式编程:代码更简洁、易读,你只需声明“要做什么”,而不是“如何去做”。
  2. 可组合性:操作可以像乐高积木一样灵活组合,构建复杂的处理流水线。
  3. 可并行化:无需编写复杂易错的多线程代码,就能轻松获得并行处理能力。
  4. 高效:得益于惰性求值,中间操作可以优化执行(如短路操作、循环合并)。

它彻底改变了 Java 程序员处理集合数据的方式,是编写现代、高效、简洁 Java 代码的必备工具。

http://www.xdnf.cn/news/20424.html

相关文章:

  • 预闪为什么可以用来防红眼?
  • C/C++动态爱心
  • Caffeine Weigher
  • 蓓韵安禧DHA纯植物藻油纯净安全零添加守护母婴健康
  • 基于STM32智能阳台监控系统
  • Unity 如何使用ModbusTCP 和PLC通讯
  • 用 Go + HTML 实现 OpenHarmony 投屏(hdckit-go + WebSocket + Canvas 实战)
  • 《sklearn机器学习——绘制分数以评估模型》验证曲线、学习曲线
  • 鸿蒙Next开发指南:UIContext接口解析与全屏拉起元服务实战
  • DevOps实战(2) - 使用Arbess+GitPuk+Docker实现Java项目自动化部署
  • Rsyslog日志采集
  • 快捷:常见ocr学术数据集预处理版本汇总(适配mmocr)
  • js闭包问题
  • B.50.10.07-分布式锁核心原理与电商应用
  • 操作系统之内存管理
  • 从 0 到 1 学 sed 与 awk:Linux 文本处理的两把 “瑞士军刀”
  • 数据结构:栈和队列(下)
  • Qt控件:Item Views/Widgets
  • 国产数据库之YashanDB:新花怒放
  • 源滚滚AI编程SillyTavern酒馆配置Claude Code API教程
  • DeepSeek vs Anthropic:技术路线的正面冲突
  • Java基础 9.5
  • centos 系统如何安装open jdk 8
  • linux下快捷删除单词、行的命令
  • python中等难度面试题(1)
  • 基于cornerstone3D的dicom影像浏览器 第五章 在Displayer四个角落显示信息
  • C++数据结构命名:从规范到艺术的深度解析
  • CSDN个人博客文章全面优化过程
  • 不同行业视角下的数据分析
  • 计算机二级C语言操作题(填空、修改、设计题)——真题库(17)附解析答案