旅游网站开发数据库,重庆市建设安全信息网,电商网站页面设计,网站技巧1. 引言
大家好#xff01;欢迎来到本系列博客的第三篇。在前两篇文章中#xff0c;我们已经领略了 Java 8 中 行为参数化 和 Lambda 表达式 的魅力。
在第 1 章 Java行为参数化#xff1a;从啰嗦到简洁的代码进化中#xff0c;我们了解到如何通过将行为#xff08;代码…1. 引言
大家好欢迎来到本系列博客的第三篇。在前两篇文章中我们已经领略了 Java 8 中 行为参数化 和 Lambda 表达式 的魅力。
在第 1 章 Java行为参数化从啰嗦到简洁的代码进化中我们了解到如何通过将行为代码块作为参数传递给方法使代码更灵活、可复用。在第 2 章 Java 8 Lambda表达式详解从入门到实践中我们深入学习了 Lambda 表达式它是实现行为参数化的简洁而强大的工具。
强烈建议先阅读前两篇文章它们为理解今天的主题——Java 8 中的“流”Streams——奠定了基础。
那么什么是“流”它为何如此重要 简而言之Java 8 的“流”提供了一种全新的、声明式的处理数据的方式。它允许你以类似于 SQL 查询的风格操作集合及其他数据源无需编写冗长的循环和条件语句。 想象一下工厂的流水线原材料数据从一端进入经过一系列处理工序操作最终产出成品。Java 8 中“流”就像这条流水线数据在其中流动我们可以通过各种“流操作”对其进行 筛选、转换、排序、分组 等。
本篇我们将深入探讨“流”的方方面面
流的定义流的特性流与集合的区别流的核心操作如何利用流编写更简洁、高效、易于理解的代码
让我们一起开启 Java 8“流”的探索之旅
2. 流是什么What are Streams?
引言中我们用流水线类比了“流”。现在让我们揭开“流”的神秘面纱。 流是“从支持数据处理操作的源生成的一系列元素”。 ——《Java 8 in Action》
让我们拆解这个定义 一系列元素: 与集合类似流也是一系列元素的集合。你可以把一堆苹果放进篮子集合也可以把它们放在流水线流上。关键在于流关注的是如何处理这些元素而不是如何存储它们。 源: 流中的元素从哪里来答案是“源”。它可以是 集合 (List, Set 等)数组I/O 资源 (文件等)生成函数 (例如产生无限序列的函数) 流本身不存储数据它只是从源头获取数据。 数据处理操作: 这是流的核心流提供了一套丰富的操作让你对数据进行各种处理类似数据库查询操作 filter: 筛选符合条件的元素。map: 将元素转换为另一种形式如小写字母转大写。reduce: 将所有元素组合成一个结果如求和。sort: 排序。… 还有很多 内部迭代: 通常我们用 for 循环或 forEach 显式遍历集合外部迭代。而流则不同它在内部迭代。你只需要告诉流_你想要做什么_无需关心_如何做_。这使代码更简洁也更容易优化如并行处理。 流不是新的数据结构而是更高层次的抽象。它专注于 做什么数据处理而不是 怎么做迭代细节。流像管道数据从源头流入经过一系列处理产生结果。这种声明式编程风格使代码更易读、维护。 3. 流与集合Streams vs. Collections
Java 8 的「流」常与集合Collections比较。虽都用于处理数据但两者差异显著。理解这些差异对于有效使用流至关重要。
相同点
存储元素 流和集合都可存储一系列元素。
不同点
特性集合 (Collections)流 (Streams)主要目的存储和访问元素对元素进行计算何时计算元素在加入集合时就已计算好元素在需要时才计算延迟计算/惰性求值迭代方式外部迭代用户代码控制迭代内部迭代流库自身控制迭代遍历次数可以多次遍历只能遍历一次数据修改可以添加、删除、修改集合中的元素流操作通常不修改数据源数据结构是一种数据结构,主要目的是以特定的时间/空间复杂度存储和访问数据不是数据结构,它没有存储空间,主要目的是对数据源进行计算。
详细解释几个关键区别
3.1 只能遍历一次
这是流的重要限制。一旦对流执行终端操作如 forEach、collect流就被“消费”不能再用。再次遍历会抛 IllegalStateException。
代码示例
ListString names Arrays.asList(Alice, Bob, Charlie);
StreamString nameStream names.stream();// 第一次遍历打印名字
nameStream.forEach(System.out::println);// 第二次遍历会抛出异常
// nameStream.forEach(System.out::println); // java.lang.IllegalStateException: stream has already been operated upon or closed这与集合形成对比集合可多次遍历。
3.2 外部迭代与内部迭代
外部迭代集合 编写显式循环如 for-each遍历集合并处理元素。你完全掌控迭代过程。内部迭代流 只需告诉流你想做什么如筛选长度大于3的名字流内部进行迭代和处理。无需编写循环代码更简洁。
代码示例
ListString names Arrays.asList(Alice, Bob, Charlie, David);// 外部迭代集合
ListString longNames1 new ArrayList();
for (String name : names) {if (name.length() 3) {longNames1.add(name);}
}
System.out.println(longNames1); // [Alice, Charlie, David]// 内部迭代流
ListString longNames2 names.stream().filter(name - name.length() 3).collect(Collectors.toList());
System.out.println(longNames2); // [Alice, Charlie, David]流内部迭代代码更简洁、易读更接近声明式编程。我们描述了想要什么筛选长度大于3的名字未指定如何做循环和条件判断。
3.3 延迟计算/惰性求值
这是流的重要特性。流的中间操作如filter,map延迟计算。遇到终端操作前中间操作不执行。终端操作触发时才计算。
4. 流操作详解 (Stream Operations in Detail)
流的强大在于其丰富的操作让你以声明式方式处理数据。操作分两类中间操作和终端操作。理解这两类操作及如何协同工作是掌握流的关键。
4.1 中间操作 (Intermediate Operations)
特点
返回另一个流 每个中间操作返回新流。可将多个中间操作链接形成“流水线”。延迟执行Lazy 中间操作不立即执行只构建流水线。终端操作触发时中间操作才执行。
常见中间操作
操作描述示例filter筛选符合条件的元素stream.filter(x - x 5)map将每个元素映射为另一个元素类型可能不同stream.map(String::toUpperCase)limit截取流的前 N 个元素stream.limit(10)skip跳过流的前 N 个元素stream.skip(5)distinct去除流中的重复元素根据 equalsstream.distinct()sorted对流中的元素排序自然排序或根据 Comparatorstream.sorted() stream.sorted(Comparator.reverseOrder())peek对流中每个元素执行一个操作但不改变流内容主要用于调试stream.peek(System.out::println)flatMap将每个元素转换为一个流,然后将这些流合并为一个流。stream.flatMap(Collection::stream)
代码示例 (中间操作链)
ListString names Arrays.asList(Alice, Bob, Charlie, David, Eve);ListString result names.stream().filter(name - name.length() 3) // 筛选长度大于3的名字.map(String::toLowerCase) // 转小写.sorted() // 排序.collect(Collectors.toList()); // 收集结果System.out.println(result); // [alice, charlie, david]filter、map、sorted 是中间操作。它们链接成流水线。注意直到 collect终端操作被调用中间操作才执行。
4.2 终端操作 (Terminal Operations)
特点
产生结果或副作用 终端操作触发流水线执行产生结果非流值或副作用如打印。消费流 终端操作执行后流被消费不能再用。
常见终端操作
操作描述示例forEach对流中每个元素执行一个操作副作用stream.forEach(System.out::println)count返回流中元素个数long count stream.count()collect将流中元素收集到集合或其他数据结构ListString list stream.collect(Collectors.toList())reduce将流中元素组合成一个值如求和、求最大值OptionalInteger sum stream.reduce(Integer::sum)anyMatch检查是否至少有一个元素匹配给定条件boolean hasLongName stream.anyMatch(s - s.length() 5)allMatch检查是否所有元素都匹配给定条件boolean allUpperCase stream.allMatch(s - Character.isUpperCase(s.charAt(0)))noneMatch检查是否没有元素匹配给定条件boolean noEmptyString stream.noneMatch(String::isEmpty)findFirst返回流中第一个元素OptionalOptionalString first stream.findFirst()findAny返回流中任意一个元素Optional并行流中更常用OptionalString any stream.findAny()
代码示例 (终端操作):
ListInteger numbers Arrays.asList(1, 2, 3, 4, 5);// 求和
int sum numbers.stream().reduce(0, Integer::sum); // 初始值为0用 Integer.sum() 累加
System.out.println(Sum: sum); // Sum: 15// 查找第一个偶数
OptionalInteger firstEven numbers.stream().filter(n - n % 2 0).findFirst();
firstEven.ifPresent(System.out::println); // 2 (若存在偶数)// 检查是否所有数字都大于0
boolean allPositive numbers.stream().allMatch(n - n 0);
System.out.println(All positive: allPositive); // All positive: true5. 流的“按需计算”On-Demand Computation
前面多次提到流的“延迟计算”/“惰性求值”。现在深入探讨。
5.1 什么是“按需计算”
流中元素只在真正需要时才计算。与集合对比集合中所有元素在创建时就已存在于内存。
5.2 为什么“按需计算”重要
带来几个关键优势 效率提升 若非所有元素都需处理“按需计算”可避免不必要计算提高效率。处理大数据集时优势明显。 短路操作 “按需计算”使“短路操作”如 findFirst、anyMatch成为可能。找到满足条件的元素就无需处理剩余元素。 无限流 “按需计算”使创建“无限流”Infinite Streams成为可能。无限流无固定结尾可根据需要生成无限多元素。
5.3 “按需计算”如何工作
通过中间操作和终端操作协同实现。
中间操作 “懒惰”。只构建处理流水线不立即执行。终端操作 “急切”。终端操作被调用触发流水线执行。
终端操作需要元素时流水线上中间操作才处理数据源。中间操作通常非一次处理一个元素而是按需逐个处理。
代码示例演示“按需计算” ListInteger numbers Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);OptionalInteger firstEvenGreaterThan5 numbers.stream().filter(n - {System.out.println(Filtering: n); // 打印过滤操作的中间结果return n % 2 0;}).filter(n - {System.out.println(Filtering again: n);return n 5;}).findFirst();firstEvenGreaterThan5.ifPresent(n - System.out.println(Result: n));输出
Filtering: 1
Filtering: 2
Filtering again: 2
Filtering: 3
Filtering: 4
Filtering again: 4
Filtering: 5
Filtering: 6
Filtering again: 6
Result: 6分析
从输出可见
并非所有数字都被 filter 处理。findFirst 找到第一个满足条件的元素6后续元素不再处理。两个filter非独立而是交替执行。
这就是“按需计算”。流只处理必要元素找到 findFirst 要求的结果。
6.总结
Java 8 的流Streams是一种强大而优雅的数据处理工具。它通过声明式、函数式的风格使代码更简洁、易读、高效。
在这篇文章中我们深入探讨了
流的本质 一种支持数据处理操作的元素序列强调“做什么”而非“怎么做”。流与集合的区别 延迟计算、内部迭代、一次性遍历等。流的操作 中间操作构建流水线和终端操作触发计算。按需计算 流的关键特性提高效率、支持短路操作和无限流。
掌握了流你就掌握了 Java 8 中最强大的武器之一。在后续的文章中我们会进一步探索流的高级用法包括并行流、自定义收集器等。敬请期待