跳到主要内容Java Stream 流常用方法 | 极客日志Javajava
Java Stream 流常用方法
系统讲解 Java Stream API 的实战用法。内容涵盖不可变集合创建数据源,多种数据源获取 Stream 流的方式,中间操作(筛选、映射、排序等)的链式调用与惰性求值特性,终结操作(统计、归约、收集等)的结果处理,以及方法引用简化 Lambda 表达式的技巧。旨在帮助开发者利用声明式编程提升代码质量与开发效率。
随缘4 浏览 Java Stream 流常用方法
在 Java 8 引入的众多特性中,Stream API 无疑是最能提升代码优雅度和开发效率的工具之一。它让我们能以声明式的方式处理数据集合(类似于 SQL 语句),告别了繁琐的 for 循环和复杂的逻辑判断。
本文将基于实战角度,带你全面掌握 Stream 流的创建、中间操作、终结操作以及方法引用的高级用法。
一、不可变集合
在开始学习 Stream 之前,了解 JDK 9+ 引入的不可变集合工厂方法非常有用,它们常被用来快速创建测试数据。
- List 和 Set: 和 ,形参依次传入 value。
List.of()
Set.of()
Map:Map.of(),形参依次传入 key 和 value。
List<String> list = List.of("张三", "李四", "王五");
Set<String> set = Set.of("A", "B", "C");
Map<String, Integer> map = Map.of("张三", 18, "李四", 20);
二、第一步:获取 Stream 流
Stream 流的生命周期始于'数据源'。不同的数据结构获取流的方式略有不同。
1. 单列集合 (List, Set)
ArrayList<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
2. 双列集合 (Map)
Map 本身不能直接获取流,需要先将其转换为单列集合(KeySet 或 EntrySet)后再获取。
HashMap<String, Integer> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
3. 数组
int[] arr = {1, 2, 3};
IntStream stream = Arrays.stream(arr);
4. 零散数据
注意:如果传入的是基本数据类型的数组(如 int[]),Stream.of 会把整个数组当成一个元素(即传递的是地址);引用类型数组则会正常展开。
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
三、第二步:Stream 中间方法 (Intermediate Operations)
中间方法具有惰性求值的特点:它们不会立即执行,只有当遇到'终结方法'时,整个流水线才会启动。
核心规则:Stream 流只能使用一次,复用会报错,建议使用链式编程。中间操作不会修改原集合的数据。
1. 筛选与切片
- limit(n):截断流,只取前 n 个数据。
- skip(n):跳过流,扔掉前 n 个数据。
注意:该方法依赖元素的 hashCode() 和 equals() 方法。如果是自定义对象,请务必重写这两个方法。
filter(Predicate):过滤。返回 false 代表当前数据舍弃,返回 true 代表保留。
list.stream().filter(ele -> ele.startsWith("张"));
2. 组合 (Concat)
- Stream.concat(a, b):将两个流合并为一个流。
3. 映射 (Map & FlatMap)
flatMap(Function):扁平化映射。主要作用是将一个流中的每个元素映射成一个新的流,然后将这些新的流合并成一个单一的流。
List<List<Integer>> listOfLists = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6)
);
List<Integer> flattenedList = listOfLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
List<String> list = Arrays.asList("张三 -20", "李四 -18");
list.stream()
.map(s -> Integer.parseInt(s.split("-")[1]))
.forEach(System.out::println);
4. 排序 (Sorted)
- sorted():根据自然顺序排序(要求元素实现
Comparable 接口)。
sorted(Comparator):通过自定义比较器排序。升序示例:
List<Person> sortedList = people.stream()
.sorted(Comparator.comparing(Person::getAge)
.thenComparing(Person::getName))
.collect(Collectors.toList());
降序示例:可以在 comparing 后面链式调用 .reversed(),或者直接使用 Comparator.reverseOrder()。
people.stream().sorted(Comparator.comparing(Person::getAge).reversed());
5. 调试 (Peek)
- peek(Consumer):类似于
forEach,但它是一个中间操作。通常用于在流的各个阶段打印日志,观察数据流向,而不打断流的处理。
四、第三步:Stream 终结方法 (Terminal Operations)
终结方法是流操作的最后一步,调用后流即关闭,无法再次使用。
1. 遍历与统计
- count():返回流中数据的个数(long 类型)。
forEach(Consumer):遍历操作,返回值为 void。
stream.forEach(ele -> System.out.println(ele));
2. 查找与匹配
- anyMatch(Predicate):判断流中是否包含任意一个满足条件的元素。
- allMatch(Predicate):判断流中是否所有元素都满足条件。
- findFirst():返回流中的第一个元素(返回类型为
Optional)。
3. 数组转换
- 不传参:返回
Object[]。
- 传参:返回指定类型的数组。
String[] arr = stream.toArray(value -> new String[value]);
String[] arr2 = stream.toArray(String[]::new);
4. 归约 (Reduce)
reduce():用于将流中的所有元素结合起来,得到一个值。例如求和、求最大值等。
Integer sum = Stream.iterate(1, x -> x + 1).limit(10)
.reduce(0, (a, b) -> a + b);
5. 收集 (Collect)
这是最常用的终结方法,将流转变为集合或其他数据结构。
- Collectors.toList() / toSet():收集到 List 或 Set 中。
Collectors.groupingBy():分组(高频用法)。根据某个分类函数对流中的元素进行分组,返回一个 Map。
Map<Integer, List<Person>> peopleByAge = people.stream()
.collect(Collectors.groupingBy(Person::getAge));
Map<Integer, Long> peopleCountByAge = people.stream()
.collect(Collectors.groupingBy(Person::getAge, Collectors.counting()));
Collectors.toMap():收集到 Map 中。
注意:toMap 如果遇到重复的 Key 会报错,建议处理 Key 冲突。
示例:将 "名字 - 性别 - 年龄" 格式的数据转为 Map,以名字为 Key,年龄为 Value。
List<String> list = Arrays.asList("张三 - 男 -20", "李四 - 女 -18");
Map<String, Integer> map = list.stream()
.collect(Collectors.toMap(
s -> s.split("-")[0],
s -> Integer.parseInt(s.split("-")[2])
));
五、进阶:方法引用 (Method References)
当 Lambda 表达式体中仅仅是调用一个已存在的方法时,可以使用方法引用 :: 来简化代码,使代码更具可读性。
1. 静态方法引用
格式:ClassName::staticMethod
list.stream().map(Integer::parseInt).collect(Collectors.toList());
2. 实例方法引用
格式:object::instanceMethod (对象名::成员方法)
StringBuilder sb = new StringBuilder();
list.stream().forEach(sb::append);
3. 特定类型的任意对象的实例方法引用
适用场景:Lambda 的第一个参数是方法的调用者,后面的参数是方法的形参。
List<String> words = Arrays.asList("apple", "banana");
List<Integer> lengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
4. 构造方法引用
List<String> names = Arrays.asList("Alice", "Bob");
List<Person> people = names.stream()
.map(Person::new)
.collect(Collectors.toList());
六、总结
Java Stream API 的核心流程可以概括为:数据源 (Source) -> 中间操作 (Intermediate) -> 终结操作 (Terminal)
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online