【Java从入门到入土】28:Stream API:告别for循环的新时代
Stream API是Java 8的核心特性之一,它彻底改变了集合数据的处理方式------从"命令式"的for循环遍历,转向"声明式"的流式操作。新手常问:"Stream和for循环有啥区别?为什么说它更优雅?并行流真的更快吗?" 今天从"为什么用Stream""怎么创建流""核心操作链"三个维度,把Stream API的设计逻辑和实战用法讲透,让你彻底告别繁琐的for循环,用更简洁的代码实现数据处理。
🤔 为什么需要Stream:声明式编程的优势
在Stream出现前,处理集合数据依赖命令式编程 (for循环):手动控制遍历、判断、转换的每一步,代码冗长且易出错;而Stream采用声明式编程:只需要描述"要做什么",无需关心"怎么做"(遍历、迭代由JDK自动优化)。
1. 命令式 vs 声明式:代码对比
需求:从员工列表中筛选出薪资>10000的研发人员,提取姓名并按薪资降序排列。
(1)for循环(命令式)
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Employee {
private String name;
private String department;
private int salary;
public Employee(String name, String department, int salary) {
this.name = name;
this.department = department;
this.salary = salary;
}
// getter/setter省略
public String getName() { return name; }
public String getDepartment() { return department; }
public int getSalary() { return salary; }
}
public class ForLoopDemo {
public static void main(String[] args) {
List<Employee> employees = initEmployees();
List<Employee> devList = new ArrayList<>();
// 1. 筛选研发部+薪资>10000
for (Employee e : employees) {
if ("研发部".equals(e.getDepartment()) && e.getSalary() > 10000) {
devList.add(e);
}
}
// 2. 按薪资降序排序
Collections.sort(devList, new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
return o2.getSalary() - o1.getSalary();
}
});
// 3. 提取姓名
List<String> nameList = new ArrayList<>();
for (Employee e : devList) {
nameList.add(e.getName());
}
System.out.println(nameList);
}
private static List<Employee> initEmployees() {
List<Employee> list = new ArrayList<>();
list.add(new Employee("张三", "研发部", 15000));
list.add(new Employee("李四", "市场部", 8000));
list.add(new Employee("王五", "研发部", 12000));
list.add(new Employee("赵六", "研发部", 9000));
return list;
}
}
(2)Stream API(声明式)
java
import java.util.List;
import java.util.stream.Collectors;
public class StreamDemo {
public static void main(String[] args) {
List<Employee> employees = initEmployees();
// 一行代码完成:筛选→排序→提取姓名
List<String> nameList = employees.stream()
.filter(e -> "研发部".equals(e.getDepartment()) && e.getSalary() > 10000) // 筛选
.sorted((o1, o2) -> o2.getSalary() - o1.getSalary()) // 排序
.map(Employee::getName) // 提取姓名
.collect(Collectors.toList()); // 收集结果
System.out.println(nameList); // [张三, 王五]
}
private static List<Employee> initEmployees() {
// 同上面的初始化逻辑
List<Employee> list = new ArrayList<>();
list.add(new Employee("张三", "研发部", 15000));
list.add(new Employee("李四", "市场部", 8000));
list.add(new Employee("王五", "研发部", 12000));
list.add(new Employee("赵六", "研发部", 9000));
return list;
}
}
2. Stream核心优势
| 特性 | 命令式(for循环) | 声明式(Stream) |
|---|---|---|
| 代码风格 | 冗长、嵌套多、可读性差 | 简洁、链式调用、语义清晰 |
| 关注点 | 怎么遍历(how) | 要做什么(what) |
| 并行处理 | 手动实现线程/锁,易出错 | 一行代码切换并行流(parallelStream()) |
| 代码复用 | 难以复用(遍历逻辑耦合) | 可封装为函数,复用性高 |
| 惰性求值 | 无(立即执行) | 有(中间操作不执行,终端操作触发) |
🚀 流的创建:集合、数组、值、函数生成
Stream的核心是"流"(数据序列),创建流是使用Stream API的第一步,JDK提供了多种创建方式,覆盖绝大多数场景。
1. 从集合创建(最常用)
所有实现Collection接口的集合(List、Set、Queue)都可以通过stream()创建串行流,parallelStream()创建并行流:
java
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamCreateFromCollection {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("Go");
// 1. 串行流
Stream<String> stream = list.stream();
// 2. 并行流(多线程处理)
Stream<String> parallelStream = list.parallelStream();
}
}
2. 从数组创建
通过Arrays.stream()创建流,支持基本类型数组(int[]、long[])和对象数组:
java
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class StreamCreateFromArray {
public static void main(String[] args) {
// 1. 对象数组
String[] arr = {"a", "b", "c"};
Stream<String> arrStream = Arrays.stream(arr);
// 2. 基本类型数组(IntStream/LongStream/DoubleStream)
int[] numArr = {1, 2, 3};
IntStream intStream = Arrays.stream(numArr);
}
}
3. 从单个/多个值创建
通过Stream.of()直接创建流,支持任意数量的参数:
java
import java.util.stream.Stream;
public class StreamCreateFromValues {
public static void main(String[] args) {
// 单个值
Stream<String> singleValueStream = Stream.of("Hello");
// 多个值
Stream<Integer> multiValueStream = Stream.of(1, 2, 3, 4);
}
}
4. 函数生成(无限流)
通过Stream.generate()(生成无限常量流)或Stream.iterate()(生成无限迭代流)创建,需配合limit()限制长度:
java
import java.util.stream.Stream;
public class StreamCreateFromFunction {
public static void main(String[] args) {
// 1. generate:生成10个随机数(Supplier接口)
Stream<Double> randomStream = Stream.generate(Math::random)
.limit(10); // 限制为10个元素,否则无限生成
randomStream.forEach(System.out::println);
// 2. iterate:生成1-10的整数(初始值+迭代规则)
Stream<Integer> iterateStream = Stream.iterate(1, n -> n + 1)
.limit(10);
iterateStream.forEach(System.out::println); // 1,2,...,10
}
}
5. 其他创建方式
- 空流:
Stream.empty()(避免返回null流); - 文件流:
Files.lines(Path)(按行读取文件内容); - 字符串流:
String.chars()(将字符串转为字符流)。
🔧 中间操作:filter、map、flatMap、sorted、distinct
Stream的操作分为"中间操作"和"终端操作":
- 中间操作:返回新的Stream,是"惰性的"(不立即执行,仅记录操作);
- 终端操作:触发流的执行,返回非Stream结果(如List、Integer、void)。
中间操作是数据处理的核心,常用的有筛选、映射、排序、去重等。
1. filter:筛选元素
根据条件过滤元素,保留Predicate返回true的元素:
java
import java.util.List;
import java.util.stream.Collectors;
public class StreamFilter {
public static void main(String[] args) {
List<Integer> nums = List.of(1, 2, 3, 4, 5, 6);
// 筛选偶数
List<Integer> evenNums = nums.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNums); // [2,4,6]
}
}
2. map:元素转换
将流中的元素映射为另一种类型(一对一转换),常用Function接口:
java
import java.util.List;
import java.util.stream.Collectors;
public class StreamMap {
public static void main(String[] args) {
List<String> strList = List.of("java", "python", "go");
// 转换为大写
List<String> upperList = strList.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(upperList); // [JAVA, PYTHON, GO]
// 数字平方
List<Integer> nums = List.of(1,2,3);
List<Integer> squareNums = nums.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println(squareNums); // [1,4,9]
}
}
3. flatMap:扁平化流
将"流中的流"扁平化为单个流(一对多转换),解决嵌套集合的遍历问题:
java
import java.util.List;
import java.util.stream.Collectors;
public class StreamFlatMap {
public static void main(String[] args) {
// 嵌套集合:[[1,2], [3,4], [5,6]]
List<List<Integer>> nestedList = List.of(
List.of(1, 2),
List.of(3, 4),
List.of(5, 6)
);
// flatMap:将每个子列表转为流,再合并为一个流
List<Integer> flatList = nestedList.stream()
.flatMap(List::stream) // 子列表→流,扁平化
.collect(Collectors.toList());
System.out.println(flatList); // [1,2,3,4,5,6]
}
}
4. sorted:排序
分为"自然排序"(元素实现Comparable)和"自定义排序"(传入Comparator):
java
import java.util.List;
import java.util.stream.Collectors;
public class StreamSorted {
public static void main(String[] args) {
// 1. 自然排序(数字升序)
List<Integer> nums = List.of(3, 1, 4, 2);
List<Integer> sortedNums = nums.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedNums); // [1,2,3,4]
// 2. 自定义排序(数字降序)
List<Integer> reverseNums = nums.stream()
.sorted((a, b) -> b - a)
.collect(Collectors.toList());
System.out.println(reverseNums); // [4,3,2,1]
// 3. 自定义对象排序(按员工薪资降序)
List<Employee> employees = initEmployees();
List<Employee> sortedEmployees = employees.stream()
.sorted((e1, e2) -> e2.getSalary() - e1.getSalary())
.collect(Collectors.toList());
}
private static List<Employee> initEmployees() {
List<Employee> list = new ArrayList<>();
list.add(new Employee("张三", "研发部", 15000));
list.add(new Employee("李四", "市场部", 8000));
list.add(new Employee("王五", "研发部", 12000));
return list;
}
}
5. distinct:去重
基于equals()方法去除重复元素,基本类型/自定义对象都支持(自定义对象需重写equals()和hashCode()):
java
import java.util.List;
import java.util.stream.Collectors;
public class StreamDistinct {
public static void main(String[] args) {
// 基本类型去重
List<Integer> nums = List.of(1, 2, 2, 3, 3, 3);
List<Integer> distinctNums = nums.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(distinctNums); // [1,2,3]
// 自定义对象去重(需重写equals/hashCode)
List<Employee> employees = List.of(
new Employee("张三", "研发部", 15000),
new Employee("张三", "研发部", 15000), // 重复元素
new Employee("李四", "市场部", 8000)
);
List<Employee> distinctEmployees = employees.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(distinctEmployees.size()); // 2
}
}
// 重写Employee的equals和hashCode
class Employee {
private String name;
private String department;
private int salary;
// 构造方法省略
public Employee(String name, String department, int salary) {
this.name = name;
this.department = department;
this.salary = salary;
}
// getter/setter省略
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return salary == employee.salary &&
Objects.equals(name, employee.name) &&
Objects.equals(department, employee.department);
}
@Override
public int hashCode() {
return Objects.hash(name, department, salary);
}
}
🎯 终端操作:collect、forEach、reduce、count
终端操作是Stream的"执行开关"------只有调用终端操作,中间操作才会执行,终端操作返回非Stream结果,且流只能被消费一次(消费后流关闭)。
1. collect:收集结果(最常用)
将流的结果收集为集合、数组、字符串等,核心依赖Collectors工具类,覆盖绝大多数收集场景:
java
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.stream.Collectors;
public class StreamCollect {
public static void main(String[] args) {
List<Employee> employees = initEmployees();
// 1. 收集为List
List<String> nameList = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
// 2. 收集为Set(自动去重)
Set<String> deptSet = employees.stream()
.map(Employee::getDepartment)
.collect(Collectors.toSet());
// 3. 收集为Map(key=姓名,value=薪资,注意key不能重复)
Map<String, Integer> nameSalaryMap = employees.stream()
.collect(Collectors.toMap(
Employee::getName,
Employee::getSalary,
(oldValue, newValue) -> oldValue // 重复key时保留旧值
));
// 4. 拼接字符串(分隔符、前缀、后缀)
String nameStr = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(", ", "员工列表:[", "]"));
System.out.println(nameStr); // 员工列表:[张三, 李四, 王五]
}
private static List<Employee> initEmployees() {
List<Employee> list = new ArrayList<>();
list.add(new Employee("张三", "研发部", 15000));
list.add(new Employee("李四", "市场部", 8000));
list.add(new Employee("王五", "研发部", 12000));
return list;
}
}
2. forEach:遍历消费
遍历流中的每个元素并执行操作(如打印、修改属性),无返回值:
java
import java.util.List;
public class StreamForEach {
public static void main(String[] args) {
List<String> list = List.of("Java", "Python", "Go");
// 1. 普通遍历
list.stream().forEach(System.out::println);
// 2. 并行流遍历(注意线程安全)
list.parallelStream().forEach(s -> System.out.println(Thread.currentThread().getName() + ": " + s));
}
}
3. reduce:归约(聚合)
将流中的元素聚合为单个值(如求和、求最大值、拼接字符串),核心是"累加器"逻辑:
java
import java.util.List;
import java.util.Optional;
public class StreamReduce {
public static void main(String[] args) {
List<Integer> nums = List.of(1, 2, 3, 4);
// 1. 求和(有初始值,返回Integer)
Integer sum = nums.stream()
.reduce(0, (a, b) -> a + b); // 初始值0,累加器:a=累计值,b=当前元素
System.out.println(sum); // 10
// 2. 求最大值(无初始值,返回Optional<Integer>,避免空指针)
Optional<Integer> max = nums.stream()
.reduce(Integer::max);
max.ifPresent(System.out::println); // 4
// 3. 拼接字符串
List<String> strList = List.of("J", "a", "v", "a");
String str = strList.stream()
.reduce("", String::concat);
System.out.println(str); // Java
}
}
4. count:统计数量
返回流中元素的个数,返回值为long类型:
java
import java.util.List;
public class StreamCount {
public static void main(String[] args) {
List<Employee> employees = initEmployees();
// 统计研发部员工数量
long devCount = employees.stream()
.filter(e -> "研发部".equals(e.getDepartment()))
.count();
System.out.println("研发部员工数:" + devCount); // 2
}
private static List<Employee> initEmployees() {
List<Employee> list = new ArrayList<>();
list.add(new Employee("张三", "研发部", 15000));
list.add(new Employee("李四", "市场部", 8000));
list.add(new Employee("王五", "研发部", 12000));
return list;
}
}
⚡ 短路操作:findFirst、anyMatch、allMatch
短路操作是终端操作的特殊类型------只要满足条件就立即停止遍历,避免全量处理,提升性能,适用于"查找符合条件的第一个元素""判断是否存在符合条件的元素"等场景。
1. findFirst:查找第一个元素
返回流中第一个元素的Optional(避免空指针),找到后立即停止遍历:
java
import java.util.List;
import java.util.Optional;
public class StreamFindFirst {
public static void main(String[] args) {
List<Employee> employees = initEmployees();
// 查找第一个研发部员工
Optional<Employee> firstDev = employees.stream()
.filter(e -> "研发部".equals(e.getDepartment()))
.findFirst();
// 安全取值(避免NullPointerException)
firstDev.ifPresent(e -> System.out.println("第一个研发部员工:" + e.getName())); // 张三
}
private static List<Employee> initEmployees() {
List<Employee> list = new ArrayList<>();
list.add(new Employee("张三", "研发部", 15000));
list.add(new Employee("李四", "市场部", 8000));
list.add(new Employee("王五", "研发部", 12000));
return list;
}
}
2. anyMatch:判断是否存在符合条件的元素
只要有一个元素满足条件,立即返回true,否则返回false:
java
import java.util.List;
public class StreamAnyMatch {
public static void main(String[] args) {
List<Employee> employees = initEmployees();
// 判断是否有薪资>12000的员工
boolean hasHighSalary = employees.stream()
.anyMatch(e -> e.getSalary() > 12000);
System.out.println(hasHighSalary); // true
// 判断是否有财务部员工
boolean hasFinance = employees.stream()
.anyMatch(e -> "财务部".equals(e.getDepartment()));
System.out.println(hasFinance); // false
}
private static List<Employee> initEmployees() {
List<Employee> list = new ArrayList<>();
list.add(new Employee("张三", "研发部", 15000));
list.add(new Employee("李四", "市场部", 8000));
list.add(new Employee("王五", "研发部", 12000));
return list;
}
}
3. allMatch:判断是否所有元素都符合条件
所有元素满足条件返回true,只要有一个不满足就立即返回false:
java
import java.util.List;
public class StreamAllMatch {
public static void main(String[] args) {
List<Employee> employees = initEmployees();
// 判断是否所有员工薪资>5000
boolean allHighSalary = employees.stream()
.allMatch(e -> e.getSalary() > 5000);
System.out.println(allHighSalary); // true
// 判断是否所有员工都是研发部
boolean allDev = employees.stream()
.allMatch(e -> "研发部".equals(e.getDepartment()));
System.out.println(allDev); // false
}
private static List<Employee> initEmployees() {
List<Employee> list = new ArrayList<>();
list.add(new Employee("张三", "研发部", 15000));
list.add(new Employee("李四", "市场部", 8000));
list.add(new Employee("王五", "研发部", 12000));
return list;
}
}
📌 核心总结
Stream API的核心是"声明式数据处理",告别繁琐的for循环,核心要点如下:
- 设计思想:声明式编程(关注what)替代命令式编程(关注how),代码更简洁、易维护;
- 流的生命周期:创建流→中间操作(惰性)→终端操作(触发执行),流只能消费一次;
- 核心操作 :
- 中间操作:filter(筛选)、map(转换)、flatMap(扁平化)、sorted(排序)、distinct(去重);
- 终端操作:collect(收集)、forEach(遍历)、reduce(归约)、count(统计);
- 短路操作:findFirst(找第一个)、anyMatch(存在即返回)、allMatch(全满足才返回);
- 性能优化:短路操作减少遍历次数,并行流(parallelStream)利用多核,但需注意线程安全;
- 最佳实践 :
- 避免在中间操作中修改外部变量(副作用);
- 自定义对象去重/排序需重写equals/hashCode或自定义Comparator;
- 优先使用Optional处理null,避免空指针。
掌握Stream API,你可以用更少的代码实现复杂的数据处理逻辑,无论是集合筛选、转换、聚合,还是并行处理,都能游刃有余------真正进入"告别for循环"的编程新时代。