Java Stream API 全面指南(完整版)
一、基础概念
1.1 什么是 Stream API?
- 定义:Java 8 引入的
java.util.stream
包,提供函数式编程风格的流式操作,用于高效处理集合、数组等数据源。 - 核心特性:
- 惰性计算:中间操作不会立即执行,直到终端操作触发。
- 无存储:Stream 本身不存储数据,而是从数据源(如集合、数组)获取数据。
- 不可复用:终端操作执行后,流对象失效,无法再次使用。
- 函数式:操作不修改源数据,而是返回新流。
1.2 Stream 与 Collection 的区别
特性 | Stream | Collection |
---|---|---|
数据存储 | 不存储数据,仅作为计算管道 | 存储数据的内存结构 |
操作效果 | 不修改源数据,返回新流 | 直接修改数据结构 |
执行时机 | 延迟执行(终端操作触发) | 立即执行 |
可复用性 | 一次消费,终端操作后失效 | 可多次遍历 |
二、Stream 的创建方式
2.1 常见创建方法
从集合创建
List<String> list = Arrays.asList("JAVA", "J2EE", "Spring");
// 返回顺序流(Stream<String>)
Stream<String> stream = list.stream();
// 返回并行流(ParallelStream<String>)
Stream<String> parallelStream = list.parallelStream();
从数组创建
int[] array = {1, 2, 3, 4};
// 返回 IntStream(基本类型流)
IntStream intStream = Arrays.stream(array);
其他数据源
// 读取文件行(返回 Stream<String>)
Stream<String> lines = Files.lines(Paths.get("file.txt"));
// 创建零散数据的流(返回 Stream<String>)
Stream<String> customStream = Stream.of("a", "b", "c");
// 返回空流(Stream.empty())
Stream<String> emptyStream = Stream.empty();
双列集合(如 Map)
Map<String, Integer> map = new HashMap<>();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
三、核心操作
3.1 中间操作(Intermediate Operations)
1. 过滤 filter
List<String> filtered = list.stream()
.filter(s -> s.length() > 3) // 过滤长度大于3的字符串
.collect(Collectors.toList());
// 结果:["J2EE", "Spring"](假设原列表是 ["JAVA", "J2EE", "Spring"])
2. 映射 map
List<Integer> lengths = list.stream()
.map(String::length) // 将字符串转换为长度
.collect(Collectors.toList());
// 结果:[4, 5, 6](对应 "JAVA" (4), "J2EE" (5), "Spring" (6))
3. 排序 sorted
List<String> sorted = list.stream()
.sorted() // 自然排序(按字母顺序)
.collect(Collectors.toList());
// 结果:["JAVA", "J2EE", "Spring"](假设原列表已有序)
4. 扁平化 flatMap
List<List<Integer>> listOfLists = Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4));
List<Integer> flattened = listOfLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
// 结果:[1, 2, 3, 4]
5. 去重 distinct
List<Integer> distinct = Arrays.asList(1, 2, 2, 3).stream()
.distinct() // 去重(依赖 equals() 和 hashCode())
.collect(Collectors.toList());
// 结果:[1, 2, 3]
3.2 终端操作(Terminal Operations)
1. 收集结果 collect
// 收集为 List(返回 List<String>)
List<String> result = list.stream().collect(Collectors.toList());
// 分组(返回 Map<Character, List<String>>)
Map<Character, List<String>> grouped = list.stream()
.collect(Collectors.groupingBy(s -> s.charAt(0)));
// 结果:{J=[JAVA, J2EE], S=[Spring]}
2. 归约 reduce
// 求和(返回 Integer)
int sum = Stream.of(1, 2, 3).reduce(0, (a, b) -> a + b);
// 结果:6
// 字符串拼接(返回 Optional<String>)
Optional<String> joined = Stream.of("a", "b", "c")
.reduce((a, b) -> a + b);
// 结果:Optional["abc"]
3. 遍历 forEach
list.stream().forEach(System.out::println);
// 输出:
// JAVA
// J2EE
// Spring
4. 匹配与统计
4.1 anyMatch
:检查是否存在匹配元素
// 检查是否有员工薪资 > 6000
boolean hasHighEarner = Emp.getEmployees().stream()
.anyMatch(emp -> emp.getSalary() > 6000);
// 结果:true(Charlie 的薪资为 7000)
4.2 allMatch
:检查所有元素是否匹配
// 检查所有员工薪资是否 > 5000
boolean allHigh = Emp.getEmployees().stream()
.allMatch(emp -> emp.getSalary() > 5000);
// 结果:false(Bob 的薪资为 4500)
4.3 noneMatch
:检查是否没有元素匹配
// 检查是否有员工薪资 < 4000
boolean noLowEarner = Emp.getEmployees().stream()
.noneMatch(emp -> emp.getSalary() < 4000);
// 结果:true(所有薪资 ≥ 4500)
4.4 findFirst
:获取第一个元素
// 按姓名排序后获取第一个员工
Optional<Emp> firstEmp = Emp.getEmployees().stream()
.sorted(Comparator.comparing(Emp::getName))
.findFirst();
// 结果:Optional[Emp{id=1, name='Alice', salary=6000}]
4.5 findAny
:获取任意一个匹配元素
// 并行流中获取任意一个薪资 > 5000 的员工
Optional<Emp> anyEmp = Emp.getEmployees().parallelStream()
.filter(emp -> emp.getSalary() > 5000)
.findAny();
// 结果:可能返回 Alice 或 Charlie(并行流结果可能不固定)
四、高级用法与最佳实践
4.1 并行流(Parallel Streams)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int parallelSum = numbers.parallelStream()
.mapToInt(Integer::intValue)
.sum();
// 结果:15(并行计算)
注意:
- 线程安全:使用
Collectors.toConcurrentMap
代替toMap
。
Map<String, Integer> concurrentMap = list.stream()
.collect(Collectors.toConcurrentMap(
s -> s,
s -> s.length()
));
// 结果:线程安全的 Map(如 ConcurrentHashMap)
4.2 自定义收集器(Collector)
// 自定义收集器:将元素收集到 ArrayList
Collector<String, List<String>, List<String>> customCollector =
Collector.of(
ArrayList::new, // 供应商函数(创建初始容器)
(list, element) -> list.add(element), // 累加器(添加元素)
(list1, list2) -> { // 组合器(合并两个容器)
list1.addAll(list2);
return list1;
},
list -> list // 结果转换器(返回最终结果)
);
List<String> collected = list.stream()
.collect(customCollector);
// 结果:与原列表相同(假设原列表是 ["JAVA", "J2EE", "Spring"])
五、常见问题与注意事项
5.1 distinct()
的依赖
class Person {
String name;
// 未重写 equals() 和 hashCode() 方法
}
List<Person> people = Arrays.asList(new Person("Alice"), new Person("Alice"));
List<Person> distinctPeople = people.stream()
.distinct()
.collect(Collectors.toList());
// 结果:可能包含重复项(因未重写 equals 和 hashCode)
5.2 并行流线程安全
// 错误示例(非线程安全的 Collectors.toMap)
Map<String, Integer> unsafeMap = list.parallelStream()
.collect(Collectors.toMap(s -> s, s -> s.length()));
// 可能抛出 ConcurrentModificationException
// 正确示例(线程安全)
Map<String, Integer> safeMap = list.parallelStream()
.collect(Collectors.toConcurrentMap(s -> s, s -> s.length()));
// 结果:线程安全的 Map(如 ConcurrentHashMap)
六、完整示例代码
6.1 复杂数据处理(使用自定义类)
class Emp {
int id;
String name;
double salary;
String sex;
public Emp(int id, String name, double salary, String sex) {
this.id = id;
this.name = name;
this.salary = salary;
this.sex = sex;
}
// 新增 getSalary 方法
public double getSalary() {
return salary;
}
// 静态方法模拟数据
public static List<Emp> getEmployees() {
return Arrays.asList(
new Emp(1, "Alice", 6000, "男"),
new Emp(2, "Bob", 4500,"男"),
new Emp(3, "Charlie", 7000, "女")
);
}
}
6.1.1 过滤高薪员工
// 过滤薪资 > 5000 的员工,按薪资降序排序
List<Emp> highEarners = Emp.getEmployees().stream()
.filter(emp -> emp.getSalary() > 5000)
.sorted((a, b) -> Double.compare(b.getSalary(), a.getSalary()))
.collect(Collectors.toList());
// 结果:
// [
// Emp{id=3, name='Charlie', salary=7000, sex="女"},
// Emp{id=1, name='Alice', salary=6000, sex="男"}
// ]
6.1.2 groupingBy
: 分组与统计
// 按姓名分组,统计薪资总和、平均值等
Map<String, DoubleSummaryStatistics> salaryStats = Emp.getEmployees().stream()
.collect(Collectors.groupingBy(
Emp::getName,
Collectors.summarizingDouble(Emp::getSalary)
));
// 结果:
// {
// "Alice" = {count=1, sum=6000.0, average=6000.0, min=6000.0, max=6000.0},
// "Bob" = {count=1, sum=4500.0, average=4500.0, min=4500.0, max=4500.0},
// "Charlie" = {count=1, sum=7000.0, average=7000.0, min=7000.0, max=7000.0}
// }
p -> emp.getSalary() > 6000);
// 结果:true(Charlie 的薪资为 7000)
6.1.3 partitioningBy
:按条件分区
Collectors.partitioningBy()
根据布尔条件将流元素分为两个组,返回 Map<Boolean, List<T>>
。
示例:按薪资是否高于 6000 分区
// 将员工分为薪资 > 6000 和 ≤ 6000 的两组
Map<Boolean, List<Emp>> partitioned = Emp.getEmployees().stream()
.collect(Collectors.partitioningBy(emp -> emp.getSalary() > 6000));
// 结果:
// {
// true = [Emp{id=3, name='Charlie', salary=7000, sex="女"}],
// false = [Emp{id=1, name='Alice', salary=6000, sex="男"}, Emp{id=2, name='Bob', salary=4500, sex="男"}]
// }
6.1.4 嵌套分区与分组(如按薪资是否高于 6000,再按性别分组):
// 先按薪资分区,再按性别分组
Map<Boolean, Map<String, List<Emp>>> partitionedAndGrouped = Emp.getEmployees().stream()
.collect(Collectors.partitioningBy(
emp -> emp.getSalary() > 6000,
Collectors.groupingBy(Emp::getSex)
));
// 结果:
// {
// true = {Charlie=[Emp{id=3, name='Charlie', salary=7000, sex="女"}]},
// false = {Alice=[...], Bob=[...]}
// }
七、总结
7.1 核心优势
- 简化操作:通过链式调用减少代码量。
- 高效处理:支持并行计算(如大数据场景)。
- 函数式编程:避免副作用,代码更易维护。
7.2 注意事项
- 线程安全:并行流需使用线程安全的收集器。
- 对象规范:使用
distinct()
时必须重写equals()
和hashCode()
。 - 不可复用性:终端操作后流对象失效,需重新创建。
7.3 分区与分组的区别
特性 | partitioningBy | groupingBy |
---|---|---|
分组依据 | 布尔条件(返回 true/false) | 任意键(如 String、Integer 等) |
返回结果 | Map<Boolean, List<T>> |
Map<K, List<T>> |
适用场景 | 二元条件判断(如是否达标) | 多元分组(如按性别、地区分组) |
关键补充说明:
Emp
类的getSalary
方法:在Emp
类中新增getSalary()
方法,确保Emp::getSalary
可用。- 常用终端操作示例:补充了
anyMatch
、allMatch
、noneMatch
、findFirst
、findAny
的完整代码和结果说明。 - 并行流的不确定性:
findAny
在并行流中的结果可能因线程执行顺序不同而变化,需注意其非确定性。 - .
partitioningBy
的核心作用:将流元素按条件分为两组,适用于简单判断场景。- 嵌套使用:可结合
groupingBy
实现多级分组(如分区后进一步分组)。 - 空值处理:即使无元素满足条件,仍会返回空列表(而非
null
)。
- 嵌套使用:可结合