Java Stream流常用方法实战指南
作者:madao哦
前言
在 Java 8 引入的众多特性中,Stream API 无疑是最能提升代码优雅度和开发效率的工具之一。它让我们能以声明式的方式处理数据集合(类似于 SQL 语句),告别了繁琐的 for 循环和复杂的逻辑判断。
本文将基于实战角度,带你全面掌握 Stream 流的创建、中间操作、终结操作以及方法引用的高级用法。
一、 不可变集合
在开始学习 Stream 之前,了解 JDK 9+ 引入的不可变集合工厂方法非常有用,它们常被用来快速创建测试数据。
List 和 Set:
List.of()和Set.of(),形参依次传入 value。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)
直接调用集合的 .stream() 方法。
ArrayList<String> list = new ArrayList<>(); Stream<String> stream = list.stream();
2. 双列集合 (Map)
Map 本身不能直接获取流,需要先将其转换为单列集合(KeySet 或 EntrySet)后再获取。
HashMap<String, Integer> map = new HashMap<>(); // 1. 对键获取流 Stream<String> keyStream = map.keySet().stream(); // 2. 对键值对获取流 (常用) Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
3. 数组
使用 Arrays 工具类的静态方法。
int[] arr = {1, 2, 3};
IntStream stream = Arrays.stream(arr);
4. 零散数据
使用 Stream.of() 静态方法。
注意:如果传入的是基本数据类型的数组(如
int[]),Stream.of会把整个数组当成一个元素(即传递的是地址);引用类型数组则会正常展开。
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
三、 第二步:Stream 中间方法 (Intermediate Operations)
中间方法具有惰性求值的特点:它们不会立即执行,只有当遇到“终结方法”时,整个流水线才会启动。
核心规则:
Stream 流只能使用一次,复用会报错,建议使用链式编程。
中间操作不会修改原集合的数据。
1. 筛选与切片
filter(Predicate):过滤。返回
false代表当前数据舍弃,返回true代表保留。// 留下 list 中以“张”开头的数据 list.stream().filter(ele -> ele.startsWith("张"));limit(n):截断流,只取前 n 个数据。
skip(n):跳过流,扔掉前 n 个数据。
distinct():元素去重。
注意:该方法依赖元素的
hashCode()和equals()方法。如果是自定义对象,请务必重写这两个方法。
2. 组合 (Concat)
Stream.concat(a, b):将两个流合并为一个流。
3. 映射 (Map & FlatMap)
map(Function):转换流中的数据类型。
// 示例:将 "张三-20" 这种字符串转为 int 类型的年龄 List<String> list = Arrays.asList("张三-20", "李四-18"); list.stream() .map(s -> Integer.parseInt(s.split("-")[1])) // 字符串转数字 .forEach(System.out::println);flatMap(Function):扁平化映射。主要作用是将一个流中的每个元素映射成一个新的流,然后将这些新的流合并成一个单一的流。
// 示例:将嵌套列表 [[1,2], [3,4], [5,6]] 展平为 [1,2,3,4,5,6] 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) // 将每个小 List 变成 Stream .collect(Collectors.toList());
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. 遍历与统计
forEach(Consumer):遍历操作,返回值为 void。
stream.forEach(ele -> System.out.println(ele));
count():返回流中数据的个数(long 类型)。
2. 查找与匹配
anyMatch(Predicate):判断流中是否包含任意一个满足条件的元素。
allMatch(Predicate):判断流中是否所有元素都满足条件。
findFirst():返回流中的第一个元素(返回类型为
Optional)。
3. 数组转换
toArray():
不传参:返回
Object[]。传参:返回指定类型的数组。
// lambda 表达式中的 value 代表数组的长度 String[] arr = stream.toArray(value -> new String[value]); // 或者使用方法引用 String[] arr2 = stream.toArray(String[]::new);
4. 归约 (Reduce)
reduce():用于将流中的所有元素结合起来,得到一个值。例如求和、求最大值等。
// 计算 1-10 的和 Integer sum = Stream.iterate(1, x -> x + 1).limit(10) .reduce(0, (a, b) -> a + b);
5. 收集 (Collect)
这是最常用的终结方法,将流转变为集合或其他数据结构。
Collectors.toList() / toSet():收集到 List 或 Set 中。
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], // Key: 名字 s -> Integer.parseInt(s.split("-")[2]) // Value: 年龄 ));Collectors.groupingBy():分组(高频用法)。 根据某个分类函数对流中的元素进行分组,返回一个 Map。
场景 1:基础分组
// 按照年龄分组,返回 Map<Integer, List<Person>> Map<Integer, List<Person>> peopleByAge = people.stream() .collect(Collectors.groupingBy(Person::getAge));场景 2:分组并统计
// 按照年龄分组,并计算每个年龄段的人数,返回 Map<Integer, Long> Map<Integer, Long> peopleCountByAge = people.stream() .collect(Collectors.groupingBy(Person::getAge, Collectors.counting()));
五、 进阶:方法引用 (Method References)
当 Lambda 表达式体中仅仅是调用一个已存在的方法时,可以使用方法引用 :: 来简化代码,使代码更具可读性。
1. 静态方法引用
格式:ClassName::staticMethod
// Lambda: s -> Integer.parseInt(s) // 引用: Integer::parseInt list.stream().map(Integer::parseInt).collect(Collectors.toList());
2. 实例方法引用
格式:object::instanceMethod (对象名::成员方法)
StringBuilder sb = new StringBuilder(); // Lambda: s -> sb.append(s) // 引用: sb::append list.stream().forEach(sb::append);
3. 特定类型的任意对象的实例方法引用
格式:ClassName::method 适用场景:Lambda 的第一个参数是方法的调用者,后面的参数是方法的形参。
List<String> words = Arrays.asList("apple", "banana");
// Lambda: (s) -> s.length() --> 相当于调用 "apple".length()
// 引用: String::length
List<Integer> lengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
4. 构造方法引用
格式:ClassName::new
List<String> names = Arrays.asList("Alice", "Bob");
// Lambda: name -> new Person(name)
// 引用: Person::new
List<Person> people = names.stream()
.map(Person::new)
.collect(Collectors.toList());
六、 总结
Java Stream API 的核心流程可以概括为: 数据源 (Source) -> 中间操作 (Intermediate) -> 终结操作 (Terminal)
到此这篇关于Java Stream流常用方法实战指南的文章就介绍到这了,更多相关Java Stream流常用方法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
