Java 8 引入的 Stream API 是 Java 函数式编程的核心利器。配合 Lambda 表达式,可以用声明式的方式处理集合数据,代码更简洁、更易读,还能轻松实现并行计算。本文从入门到实战,带你全面掌握 Stream。
一、Stream 是什么
1.1 概念
Stream(流)是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
特点:
- Stream 自己不存储元素,只是对数据进行计算
- Stream 操作不会改变源数据,每次操作返回新的 Stream
- 操作是延迟执行的,只有触发终止操作时才会真正执行
- 流只能被消费一次,遍历完就用完了
1.2 操作分类
1 2 3 4 5 6 7
| Stream 操作 ├── 中间操作(Intermediate)—— 返回新 Stream,延迟执行 │ ├── 无状态:filter、map、flatMap、peek │ └── 有状态:distinct、sorted、limit、skip └── 终止操作(Terminal)—— 触发计算,返回最终结果 ├── 非短路:forEach、collect、reduce、count └── 短路:anyMatch、allMatch、findFirst、findAny
|
二、创建 Stream 的四种方式
2.1 通过集合创建(最常用)
1 2 3 4 5 6 7
| List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream();
|
2.2 通过数组创建
1 2 3 4 5
| String[] array = {"apple", "banana", "cherry"}; Stream<String> stream = Arrays.stream(array);
Stream<String> partStream = Arrays.stream(array, 0, 2);
|
2.3 通过 Stream.of
1
| Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
|
2.4 创建无限流
1 2 3 4 5
| Stream<Integer> iterate = Stream.iterate(0, x -> x + 2);
Stream<Double> generate = Stream.generate(Math::random);
|
三、中间操作
3.1 filter — 过滤
筛选符合条件的元素:
1 2 3 4 5 6 7
| List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evens = list.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList());
|
3.2 map — 映射转换
将元素转换为另一种形式:
1 2 3 4 5 6 7 8 9 10 11 12 13
| List<String> names = Arrays.asList("zhangsan", "lisi", "wangwu");
List<String> upperNames = names.stream() .map(String::toUpperCase) .collect(Collectors.toList());
List<Integer> lengths = names.stream() .map(String::length) .collect(Collectors.toList());
|
3.3 flatMap — 扁平化映射
将流中每个元素都换成另一个流,然后把所有流合成一个流:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| List<String> words = Arrays.asList("hello", "world");
List<Stream<String>> result1 = words.stream() .map(word -> word.split("")) .map(Arrays::stream) .collect(Collectors.toList());
List<String> result2 = words.stream() .map(word -> word.split("")) .flatMap(Arrays::stream) .collect(Collectors.toList());
|
3.4 distinct — 去重
1 2 3 4 5 6
| List<Integer> list = Arrays.asList(1, 2, 2, 3, 3, 3, 4);
List<Integer> unique = list.stream() .distinct() .collect(Collectors.toList());
|
3.5 sorted — 排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| List<Integer> list = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);
List<Integer> sorted1 = list.stream() .sorted() .collect(Collectors.toList());
List<Integer> sorted2 = list.stream() .sorted(Comparator.reverseOrder()) .collect(Collectors.toList());
List<Employee> sortedEmp = employees.stream() .sorted(Comparator.comparing(Employee::getAge)) .collect(Collectors.toList());
|
3.6 limit & skip — 截断与跳过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> limit = list.stream() .limit(3) .collect(Collectors.toList());
List<Integer> skip = list.stream() .skip(5) .collect(Collectors.toList());
List<Integer> page = list.stream() .skip(3) .limit(3) .collect(Collectors.toList());
|
3.7 peek — 调试用
和 forEach 类似,但属于中间操作,常用于调试:
1 2 3 4 5
| List<Integer> result = Stream.of(1, 2, 3) .peek(n -> System.out.println("原数据: " + n)) .map(n -> n * 2) .peek(n -> System.out.println("翻倍后: " + n)) .collect(Collectors.toList());
|
四、终止操作
4.1 forEach — 遍历
1
| list.stream().forEach(System.out::println);
|
4.2 count — 计数
1
| long count = list.stream().count();
|
4.3 max / min — 最值
1 2 3 4 5 6
| Optional<Integer> max = list.stream().max(Integer::compareTo);
Optional<Employee> oldest = employees.stream() .max(Comparator.comparing(Employee::getAge));
|
4.4 findFirst / findAny — 查找
1 2 3 4 5
| Optional<Integer> first = list.stream().findFirst();
Optional<Integer> any = list.parallelStream().findAny();
|
4.5 匹配操作
1 2 3 4 5 6 7 8
| boolean allEven = list.stream().allMatch(n -> n % 2 == 0);
boolean hasEven = list.stream().anyMatch(n -> n % 2 == 0);
boolean noneNegative = list.stream().noneMatch(n -> n < 0);
|
4.6 reduce — 归约
将流中元素反复结合,得到一个值:
1 2 3 4 5 6 7 8 9 10 11 12 13
| List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Integer sum = list.stream().reduce(0, Integer::sum);
Optional<Integer> max = list.stream().reduce(Integer::max);
List<String> words = Arrays.asList("Hello", "Stream", "World"); String joined = words.stream().reduce("", (a, b) -> a + " " + b);
|
五、Collectors 收集器(重点)
collect 是最常用的终止操作,配合 Collectors 工具类可以实现各种收集需求。
5.1 转集合
1 2 3 4 5 6 7 8
| List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());
LinkedList<String> linkedList = stream.collect(Collectors.toCollection(LinkedList::new));
|
5.2 转 Map
1 2 3 4 5 6 7 8 9 10 11
| Map<Long, Employee> empMap = employees.stream() .collect(Collectors.toMap( Employee::getId, Function.identity(), (v1, v2) -> v1 ));
Map<Long, String> idNameMap = employees.stream() .collect(Collectors.toMap(Employee::getId, Employee::getName));
|
5.3 分组 groupingBy
按某个字段分组,是开发中最常用的操作之一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| Map<String, List<Employee>> deptMap = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment));
Map<String, Map<String, List<Employee>>> multiGroup = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.groupingBy(emp -> emp.getAge() > 30 ? "中年" : "青年" ) ));
Map<String, Long> deptCount = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.counting() ));
Map<String, Double> deptAvgSalary = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.averagingDouble(Employee::getSalary) ));
|
5.4 分区 partitioningBy
特殊的分组,只有 true/false 两组:
1 2 3 4 5 6 7
| Map<Boolean, List<Employee>> partition = employees.stream() .collect(Collectors.partitioningBy(emp -> emp.getSalary() > 10000));
List<Employee> highSalary = partition.get(true); List<Employee> normalSalary = partition.get(false);
|
5.5 聚合统计
1 2 3 4 5 6 7 8 9
| IntSummaryStatistics stats = employees.stream() .collect(Collectors.summarizingInt(Employee::getAge));
stats.getCount(); stats.getSum(); stats.getMin(); stats.getAverage(); stats.getMax();
|
5.6 拼接字符串
1 2 3 4 5 6 7 8 9
| String names = employees.stream() .map(Employee::getName) .collect(Collectors.joining(", "));
String result = words.stream() .collect(Collectors.joining(", ", "[", "]"));
|
六、实战案例
案例一:从员工列表中筛选统计
1 2 3 4 5 6 7 8
| List<String> top3Names = employees.stream() .filter(emp -> "研发部".equals(emp.getDepartment())) .filter(emp -> emp.getSalary() > 8000) .sorted(Comparator.comparing(Employee::getSalary).reversed()) .limit(3) .map(Employee::getName) .collect(Collectors.toList());
|
案例二:计算每个部门的薪资总和
1 2 3 4 5
| Map<String, Double> deptTotalSalary = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.summingDouble(Employee::getSalary) ));
|
案例三:嵌套集合扁平化
1 2 3 4 5 6
| List<String> allHobbies = classes.stream() .flatMap(c -> c.getStudents().stream()) .flatMap(s -> s.getHobbies().stream()) .distinct() .collect(Collectors.toList());
|
案例四:List 转树形结构
1 2 3 4 5 6 7 8 9 10 11 12 13
| List<Menu> menuTree = menuList.stream() .filter(menu -> menu.getParentId() == 0) .peek(menu -> menu.setChildren(getChildren(menu, menuList))) .collect(Collectors.toList());
private List<Menu> getChildren(Menu parent, List<Menu> all) { return all.stream() .filter(menu -> menu.getParentId().equals(parent.getId())) .peek(menu -> menu.setChildren(getChildren(menu, all))) .collect(Collectors.toList()); }
|
七、并行流
7.1 使用方式
1 2 3 4 5
| list.parallelStream()
list.stream().parallel()
|
7.2 适用场景
- 数据量大、计算密集型任务
- 每个元素计算独立,无状态依赖
- CPU 多核能发挥优势
7.3 注意事项
1 2 3 4 5 6
| int[] sum = {0}; list.parallelStream().forEach(n -> sum[0] += n);
Integer sum = list.parallelStream().reduce(0, Integer::sum);
|
- 并行流默认使用
ForkJoinPool.commonPool(),全局共享
- I/O 密集型任务不适合并行流(线程阻塞反而浪费资源)
- 小数据量不要用并行流,线程切换开销大于收益
八、Optional 配合使用
Stream 很多操作返回 Optional,配合使用能优雅避免空指针:
1 2 3 4 5 6
| String name = employees.stream() .filter(emp -> emp.getSalary() > 10000) .findFirst() .map(Employee::getName) .orElse("未找到");
|
Stream 是 Java 8 最实用的特性之一,熟练使用能大幅提升代码质量。但也要注意:不要为了用 Stream 而强行写复杂的一行流,可读性永远是第一位的。复杂逻辑拆成几步,比一行难以理解的长链更利于维护。