Stream API
面向对象程序设计(Java)
告别繁琐的循环,拥抱函数式编程传统集合处理的痛点
场景:处理一个学生列表,找出年龄>18的女生,按姓名排序,提取姓名列表
🔴 传统写法的问题:
// 需要多个中间变量和循环
List<Student> adults = new ArrayList<>();
for (Student s : students) {
if (s.getAge() > 18 && s.getGender() == FEMALE) {
adults.add(s);
}
}
// 还需要手动排序
adults.sort(Comparator.comparing(Student::getName));
// 还需要再提取姓名
List<String> names = new ArrayList<>();
for (Student s : adults) {
names.add(s.getName());
}
😤 痛点总结:
- 代码冗长:需要多个临时集合和多层循环
- 命令式思维:需要告诉计算机"如何做",而非"做什么"
- 容易出错:手动管理状态,边界条件处理复杂
- 可读性差:业务逻辑被循环语法淹没
- 难以并行化:传统循环需要重写才能并行处理
"要是能这样做就好了!"
如果我们能用类似下面的伪代码来表达数据处理逻辑,那该多好啊!
💭 理想的伪代码表达:
// 🎯 理想方式:声明式表达
成年女生姓名 = 从(students)
中筛选(年龄 >= 18 且 性别 == "女")
按姓名排序()
提取姓名()
收集到列表();
// 🎯 稍微规范化一点:链式调用
result = students
.filter(年龄 >= 18)
.filter(性别 == "女")
.sortBy(姓名)
.map(提取姓名)
.collect(转成列表);
✨ 伪代码的优势:
- 接近自然语言:像说话一样描述数据处理逻辑
- 关注意图:关心"做什么"而不是"怎么做"
- 无中间变量:不需要临时的集合和计数器
- 链式调用:多个操作可以一气呵成
- 简洁优雅:同样的逻辑,代码量减少70%
🚀 伪代码对应的实际Stream代码:
// Java Stream API - 理想照进现实!
List<String> names = students.stream()
.filter(student -> student.getAge() >= 18) // 筛选:年龄 >= 18
.filter(student -> student.getGender() == FEMALE) // 筛选:性别 == 女
.sorted(Comparator.comparing(Student::getName)) // 排序:按姓名
.map(Student::getName) // 提取:姓名
.collect(Collectors.toList()); // 收集:转成列表
// 等价于一行表达式的写法
List<String> names = students.stream()
.filter(s -> s.getAge() >= 18 && s.getGender() == FEMALE)
.sorted(Comparator.comparing(Student::getName))
.map(Student::getName)
.collect(Collectors.toList());
💡 关键思维转变:
| 传统思维 | Stream思维 |
|---|---|
| 告诉计算机如何做 | 告诉计算机做什么 |
| 关注过程和步骤 | 关注结果和转换 |
| 需要显式管理中间状态 | 框架自动管理数据流 |
| 修改原数据或创建临时变量 | 通过不可变链生成新结果 |
Stream API 就是把这些理想伪代码变成了现实!
让数据处理变得像写SQL查询一样优雅简洁!
如何构造 Stream
Stream 可以通过多种数据源创建,最常用的是从集合(Collection)创建。
-
1. 从集合 (Collection) - 最常用的方式
List<String> list = List.of("Java", "Python", "Go"); Stream<String> stream = list.stream(); -
2. 从数组 (Array)
String[] arr = {"Java", "Python", "Go"}; Stream<String> stream = Arrays.stream(arr); -
3. 直接通过值 (Stream.of)
Stream<String> stream = Stream.of("Java", "Python", "Go"); -
4. 构造无限流 (Infinite Streams)
Stream<Double> randoms = Stream.generate(Math::random).limit(5); Stream<Integer> evens = Stream.iterate(0, n -> n + 2).limit(10);
* 深入理解无限流
无限流是 Stream API 的一大特色,允许定义无穷的数据源,只有在需要时才计算(延迟执行)。
1. Stream.generate(Supplier<T> s)
机制: 基于 Supplier
接口(无参,有返回值),单纯地"生产"数据,不依赖上一次生成的结果。
适用场景: 生成随机数、常量流、UUID 等无规律数据。
// 示例:生成 3 个随机 UUID
// UUID::randomUUID 符合 Supplier<UUID> 签名
Stream.generate(java.util.UUID::randomUUID)
.limit(3)
.forEach(System.out::println);
// 输出示例: e58ed763-928c-4155-bee9-fdbaaadc15f3 ...
2. Stream.iterate(T seed, UnaryOperator<T> f)
机制: 这是一个迭代过程。从种子值 seed 开始,将函数
f 反复应用(seed, f(seed), f(f(seed))...)。
适用场景: 生成等差/等比数列、时间序列、斐波那契数列等有规律数据。
// 示例:生成倍数序列 (1, 2, 4, 8, 16...)
// n -> n * 2 符合 UnaryOperator<Integer> 签名 (输入=输出)
Stream.iterate(1, n -> n * 2)
.limit(5)
.forEach(System.out::println);
// 过程: 1 -> 2 -> 4 -> 8 -> 16
limit() 或其他短路操作(Short-circuiting)使用,否则一旦执行终端操作(如
forEach),程序将陷入死循环!
Stream API 全景视图
🗺️ 操作分类体系
按功能核心可分为以下6大类别:
| 类别 | 核心方法 | 主要用途 |
|---|---|---|
| 1. 筛选切片 | filter, distinct, limit, skip, take/dropWhile |
过滤、去重、截取 |
| 2. 映射加工 | map, flatMap, peek |
转换、扁平化、调试 |
| 3. 排序操作 | sorted |
排序 |
| 4. 查找匹配 | any/all/noneMatch, findFirst/Any |
条件检查、查找 |
| 5. 遍历收集 | collect, forEach(Ordered), toArray |
收集、遍历、数组 |
| 6. 归约统计 | reduce, count, min/max |
聚合、统计、最值 |
| 7. 构建/模式 | concat, parallel, unordered, onClose |
合并、并行、无序 |
🧠 记忆心法:SWORD 全景模型
- ⚔️ Shape (整形):
sorted,limit/skip,distinct,take/dropWhile - ⚔️ Where (条件):
filter,match,find - ⚔️ Operation (加工):
map,flatMap,peek - ⚔️ Reduce (聚合):
reduce,count,min/max - ⚔️ Destination (终端):
collect,forEach,toArray
🏗️ 流水线三大铁律
- Source 开启: 集合/数组/IO
- Intermediate 惰性求值: 只配置不执行
- Terminal 触发执行: 必须且唯一
🎯 终端操作 (Terminal)
判别标准:返回值不是 Stream (如 void, List, long)。
- 流是一次性的:像水流一样只去不回,不可复用。
- 必须有终端操作,否则中间操作不会执行。
🚀 最佳实践
原则:先过滤,后处理 (尽早减少数据量)
stream
.filter(s -> s.score > 60) // ✅ 先过滤
.map(s -> complex(s)) // ✅ 后运算
.collect(toList()); // ✅ 终结
[T] 遍历:forEach()
📋 方法签名
void forEach(Consumer<? super T> action)
参数说明:
- Consumer<? super T> action - 对流中每个元素执行的操作,是一个消费型函数式接口
返回值:
void - 无返回值,这是一个终端操作
💡 使用提示:
forEach是终端操作,调用后Stream结束。适合遍历处理,但不能改变Stream中的元素。在并行Stream中要注意线程安全问题。
forEach() 用于遍历Stream中的每个元素,是Stream中最基本的操作。
🔴 传统写法的痛点:
// 传统的for-each循环
for (Student student : students) {
System.out.println("学生姓名: " + student.getName());
System.out.println("学生年龄: " + student.getAge());
}
// 传统for循环(需要处理索引)
for (int i = 0; i < students.size(); i++) {
Student student = students.get(i);
System.out.println("第" + (i + 1) + "个学生: " + student.getName());
}
🟢 Stream forEach() 解决方案:
// 简洁的forEach
students.stream()
.forEach(student -> {
System.out.println("学生姓名: " + student.getName());
System.out.println("学生年龄: " + student.getAge());
});
// 使用方法引用(更简洁)
students.stream()
.forEach(System.out::println);
// 带索引的遍历(需要额外处理)
AtomicInteger index = new AtomicInteger(0);
students.stream()
.forEach(student -> {
System.out.println("第" + (index.incrementAndGet()) + "个学生: " + student.getName());
});
💡 使用场景和注意事项:
- 遍历处理:需要对每个元素执行相同操作时
- 终端操作:forEach是终端操作,调用后Stream结束
- 无返回值:不能在forEach中返回结果,适合副作用操作
- 并行安全:在并行Stream中要注意线程安全问题
forEach() 让遍历操作更加简洁,但要注意它是终端操作且不能改变Stream中的元素!
筛选:filter()
📋 方法签名
Stream<T> filter(Predicate<? super T> predicate)
参数说明:
- Predicate<? super T> predicate - 断言型函数式接口,返回true表示保留元素,false表示过滤掉
返回值:
Stream<T> - 包含所有满足predicate条件的元素的新Stream
💡 使用提示:
filter是中间操作,是惰性求值的。可以链式调用多个filter来组合复杂的筛选条件。通常将筛选操作放在链式调用的前面以减少处理的数据量。
筛选操作用于根据条件过滤元素,是Stream中最常用的操作之一。
🔍 filter() - 条件过滤
// 传统写法 - 繁琐
List<Student> adults = new ArrayList<>();
for (Student s : students) {
if (s.getAge() >= 18) {
adults.add(s);
}
}
// Stream写法 - 简洁
List<Student> adults = students.stream()
.filter(student -> student.getAge() >= 18)
.collect(Collectors.toList());
// 复杂条件筛选
List<Student> qualifiedStudents = students.stream()
.filter(s -> s.getAge() >= 18 && s.getScore() > 80 && s.isActive())
.collect(Collectors.toList());
// 多次filter链式调用(可读性更好)
List<Student> result = students.stream()
.filter(s -> s.getAge() >= 18) // 年龄筛选
.filter(s -> s.getScore() > 80) // 成绩筛选
.filter(Student::isActive) // 状态筛选
.collect(Collectors.toList());
筛选:distinct()
📋 方法签名
Stream<T> distinct()
参数说明:
- 无参数 - 使用元素的equals()和hashCode()方法进行去重判断
返回值:
Stream<T> - 去重后的新Stream,对于有序流保持原始顺序
💡 使用提示:
distinct是中间操作,会去除流中的重复元素。需要元素正确实现equals()和hashCode()方法。对于大型数据集,distinct()的性能开销可能较大。
筛选操作用于根据条件过滤元素,是Stream中最常用的操作之一。
🔄 distinct() - 去重筛选
// 传统写法 - 需要Set辅助
Set<String> uniqueNames = new HashSet<>();
List<String> uniqueNameList = new ArrayList<>();
for (Student s : students) {
String name = s.getName();
if (uniqueNames.add(name)) { // Set.add()返回true表示元素不存在
uniqueNameList.add(name);
}
}
// Stream写法 - 优雅
List<String> uniqueNames = students.stream()
.map(Student::getName)
.distinct()
.collect(Collectors.toList());
// 对自定义对象去重(需要重写equals和hashCode)
List<Student> uniqueStudents = students.stream()
.distinct()
.collect(Collectors.toList());
* 筛选:take/dropWhile()
📋 方法签名
Stream<T> takeWhile(Predicate<? super T> predicate)
Stream<T> dropWhile(Predicate<? super T> predicate)
参数说明:
- Predicate<? super T> predicate - 断言条件,用于判断是否继续处理元素
返回值:
Stream<T> - 返回截取后的新Stream
💡 使用提示:
takeWhile()取元素直到遇到第一个不满足条件的元素;dropWhile()跳过元素直到遇到第一个不满足条件的元素。这两个都是短路操作,可以提升性能。注意:对于无序流,行为可能不确定。
takeWhile() 和 dropWhile() 是 Java 9 新增的短路操作,用于按条件截取数据流。
✂️ takeWhile() - 满足条件时取元素
// 传统写法 - 需要手动控制循环
List<Integer> result = new ArrayList<>();
for (Integer num : numbers) {
if (num < 4) {
result.add(num);
} else {
break; // 遇到不满足的条件就停止
}
}
// Stream写法 - 简洁的短路操作
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 1, 2);
List<Integer> result = numbers.stream()
.takeWhile(n -> n < 4) // 取直到遇到不满足条件的元素
.collect(Collectors.toList());
// 结果: [1, 2, 3] (遇到4就停止,不会继续取后面的1,2)
🗑️ dropWhile() - 丢弃满足条件的元素
// 传统写法 - 需要标志位控制
List<Integer> result = new ArrayList<>();
boolean startTaking = false;
for (Integer num : numbers) {
if (!startTaking && num >= 4) {
startTaking = true;
}
if (startTaking) {
result.add(num);
}
}
// Stream写法 - 优雅的短路操作
List<Integer> result2 = numbers.stream()
.dropWhile(n -> n < 4) // 丢弃直到遇到不满足条件的元素
.collect(Collectors.toList());
// 结果: [4, 5, 1, 2] (从4开始取,包括后面的所有元素)
💡 实际应用场景:
// 场景1:处理排序后的数据,取连续满足条件的部分
List<Student> students = getStudentsByScore(); // 已按成绩排序
List<Student> topStudents = students.stream()
.takeWhile(s -> s.getScore() >= 90) // 取连续90分以上的学生
.collect(Collectors.toList());
// 场景2:跳过日志文件的头部信息
List<String> dataLines = Files.lines(logFile)
.dropWhile(line -> line.startsWith("#")) // 跳过注释行
.collect(Collectors.toList());
// 场景3:处理时间序列数据
List<Double> temperatures = getTemperatureReadings();
List<Double> normalTemp = temperatures.stream()
.takeWhile(t -> t >= 36.0 && t <= 37.5) // 取正常体温数据
.collect(Collectors.toList());
- 这是 Java 9+ 的特性,需要 Java 9 或更高版本
- 与
filter()不同:filter()处理所有元素,而takeWhile/dropWhile()是短路操作 - 对于 有序流,行为是确定的;对于 无序流,结果可能不同
takeWhile() 和 dropWhile() 提供了强大的条件截取能力,特别适合处理有序数据和需要短路操作的场景!
映射:map()
📋 方法签名
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
参数说明:
- Function<? super T, ? extends R> mapper - 函数式接口,将类型为T的元素转换为类型为R的元素
返回值:
Stream<R> - 包含转换后元素的新Stream,元素类型可能不同
💡 使用提示:
map是中间操作,可以改变Stream中元素的类型。常用于数据转换、对象属性提取、格式转换等场景。map操作是一对一的映射,每个输入元素只产生一个输出元素。
映射操作用于转换Stream中的元素类型或结构,是Stream API的核心功能。
🔄 map() - 类型转换
// 传统写法 - 需要显式循环
List<String> names = new ArrayList<>();
for (Student student : students) {
names.add(student.getName());
}
// Stream写法 - 简洁
List<String> names = students.stream()
.map(Student::getName)
.collect(Collectors.toList());
// 复杂转换
List<StudentDTO> dtos = students.stream()
.map(student -> {
StudentDTO dto = new StudentDTO();
dto.setName(student.getName());
dto.setAge(student.getAge());
return dto;
})
.collect(Collectors.toList());
// 多步转换
List<Integer> nameLengths = students.stream()
.map(Student::getName) // Student -> String
.map(String::length) // String -> Integer
.collect(Collectors.toList());
🔢 基本类型映射:
// 基本类型映射 - 避免自动装箱
IntStream ages = students.stream()
.mapToInt(Student::getAge);
DoubleStream scores = students.stream()
.mapToDouble(Student::getScore);
LongStream ids = students.stream()
.mapToLong(Student::getId);
// 基本类型Stream的优势
double averageAge = students.stream()
.mapToInt(Student::getAge)
.average()
.orElse(0.0); // 无需Optional处理
映射操作是Stream的核心,map()用于一对一转换,flatMap()用于一对多扁平化!
映射:flatMap()
📋 方法签名
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
参数说明:
- Function<? super T, ? extends Stream<? extends R>> mapper - 将每个元素映射为一个Stream的函数式接口
返回值:
Stream<R> - 扁平化后的新Stream,包含所有子Stream的元素
💡 使用提示:
flatMap是中间操作,实现一对多映射。常用于处理嵌套集合、数组展开、对象扁平化等场景。flatMap会自动将多个Stream合并为一个Stream。
映射操作用于转换Stream中的元素类型或结构,是Stream API的核心功能。
📦 flatMap() - 扁平化映射
// 场景:学生选课列表,获取所有课程名称
List<Student> students = getStudents();
// 传统写法 - 嵌套循环
List<String> allCourses = new ArrayList<>();
for (Student student : students) {
for (Course course : student.getCourses()) {
allCourses.add(course.getName());
}
}
// Stream写法 - 使用flatMap扁平化
List<String> allCourses = students.stream()
.flatMap(student -> student.getCourses().stream()) // 将List<Course>扁平化为Stream<Course>
.map(Course::getName)
.distinct()
.collect(Collectors.toList());
// 字符串分割并扁平化
List<String> words = Arrays.asList("hello world", "java stream", "functional programming")
.stream()
.flatMap(sentence -> Arrays.stream(sentence.split(" ")))
.distinct()
.collect(Collectors.toList());
// 结果: ["hello", "world", "java", "stream", "functional", "programming"]
映射操作是Stream的核心,map()用于一对一转换,flatMap()用于一对多扁平化!
调试:peek()
📋 方法签名
Stream<T> peek(Consumer<? super T> action)
参数说明:
- Consumer<? super T> action - 对Stream中每个元素执行的操作,通常用于调试
返回值:
Stream<T> - 原始Stream,peek操作不会修改Stream内容
💡 使用提示:
peek是中间操作,主要用于调试和监控。可以在Stream处理链中插入查看点,但不应修改元素状态。适合配合日志输出或断点调试使用。
peek() 是一个中间操作,主要用于调试Stream处理过程,让我们能够查看每个处理阶段的数据。
🔍 peek() - 调试Stream处理
// 传统调试方式 - 需要打断点或打印变量
List<String> names = new ArrayList<>();
for (Student student : students) {
if (student.getAge() >= 18) {
System.out.println("过滤后的学生: " + student.getName()); // 调试输出
names.add(student.getName());
}
}
// Stream使用peek()调试 - 链式调试
List<String> result = students.stream()
.filter(student -> student.getAge() >= 18)
.peek(student -> System.out.println("过滤后: " + student.getName()))
.map(Student::getName)
.peek(name -> System.out.println("映射后: " + name))
.filter(name -> name.length() > 2)
.collect(Collectors.toList());
🛠️ peek() 的多种调试用途
// 用途1:跟踪数据转换过程
List<Integer> squares = numbers.stream()
.peek(n -> System.out.println("原始: " + n))
.map(n -> n * n)
.peek(n -> System.out.println("平方: " + n))
.filter(n -> n > 50)
.peek(n -> System.out.println("过滤后: " + n))
.collect(Collectors.toList());
// 用途2:验证数据流内容
Set<String> categories = products.stream()
.peek(p -> {
if (p.getCategory() == null) {
System.out.println("警告: 产品 " + p.getName() + " 没有分类");
}
})
.filter(p -> p.getCategory() != null)
.map(Product::getCategory)
.collect(Collectors.toSet());
// 用途3:收集统计信息(副作用)
AtomicInteger count = new AtomicInteger(0);
List<Student> processed = students.stream()
.peek(student -> count.incrementAndGet())
.filter(Student::isActive)
.collect(Collectors.toList());
System.out.println("处理了 " + count.get() + " 个学生");
⚠️ 重要注意事项
// ❌ 错误:在peek中修改状态(副作用)
List<Student> result = students.stream()
.peek(student -> student.setScore(student.getScore() + 10)) // 不要这样做!
.collect(Collectors.toList());
// ✅ 正确:在peek中只进行调试或观察
List<Student> result = students.stream()
.peek(student -> System.out.println("处理: " + student.getName()))
.map(student -> {
Student newStudent = new Student(student);
newStudent.setScore(student.getScore() + 10); // 在map中创建新对象
return newStudent;
})
.collect(Collectors.toList());
// ✅ 正确:使用peek进行性能监控
long start = System.currentTimeMillis();
List<Result> results = data.stream()
.peek(d -> {
if (System.currentTimeMillis() - start > 5000) {
System.out.println("警告:处理超过5秒");
}
})
.map(this::expensiveOperation)
.collect(Collectors.toList());
- 调试专用:peek主要用于调试,避免在生产代码中使用
- 无副作用:不要在peek中修改Stream中的元素
- 性能影响:调试完成后记得移除peek调用
- 日志级别:使用适当的日志级别而非System.out
🔧 高级调试技巧
// 技巧1:条件调试 - 只在特定条件下打印
students.stream()
.peek(student -> {
if ("计算机科学".equals(student.getMajor())) {
System.out.println("计算机专业学生: " + student.getName());
}
})
.collect(Collectors.toList());
// 技巧2:统计调试 - 收集处理信息
Map<String, AtomicLong> counters = new HashMap<>();
students.stream()
.peek(student ->
counters.computeIfAbsent(student.getMajor(), k -> new AtomicLong())
.incrementAndGet())
.collect(Collectors.toList());
// 技巧3:断言调试 - 验证数据完整性
students.stream()
.peek(student -> {
assert student.getName() != null : "学生姓名不能为空";
assert student.getAge() >= 0 : "学生年龄不能为负数";
})
.collect(Collectors.toList());
peek() 是Stream调试的利器,让我们能够清晰地看到数据流的每个处理步骤!
排序:sorted()
📋 方法签名
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
参数说明:
- Comparator<? super T> comparator - 比较器,用于定义排序规则(可选参数)
返回值:
Stream<T> - 排序后的新Stream
💡 使用提示:
sorted是中间操作,有参数版本使用自定义比较器,无参数版本要求元素实现Comparable接口。sorted是状态ful操作,需要处理所有元素才能完成排序,对性能有影响。
排序操作用于对Stream中的元素进行排序,支持自然排序和自定义排序。
📊 sorted() - 基础排序
// 传统写法 - 修改原集合
List<Student> students = new ArrayList<>(originalStudents);
Collections.sort(students);
// 或者使用Comparator
students.sort(Comparator.comparing(Student::getScore));
// Stream写法 - 不修改原集合
List<Student> sortedStudents = students.stream()
.sorted() // 自然排序(需要实现Comparable)
.collect(Collectors.toList());
// 使用Comparator排序
List<Student> sortedByScore = students.stream()
.sorted(Comparator.comparing(Student::getScore))
.collect(Collectors.toList());
// 降序排序
List<Student> sortedByScoreDesc = students.stream()
.sorted(Comparator.comparing(Student::getScore).reversed())
.collect(Collectors.toList());
🎯 复杂排序规则:
// 多级排序:先按年龄,再按成绩,最后按姓名
List<Student> multiSort = students.stream()
.sorted(Comparator
.comparing(Student::getAge) // 一级排序:年龄
.thenComparing(Student::getScore) // 二级排序:成绩
.thenComparing(Student::getName)) // 三级排序:姓名
.collect(Collectors.toList());
// 自定义排序规则
List<Student> customSort = students.stream()
.sorted((s1, s2) -> {
// 自定义复杂排序逻辑
int scoreCompare = Double.compare(s2.getScore(), s1.getScore()); // 成绩降序
if (scoreCompare != 0) return scoreCompare;
return s1.getName().compareTo(s2.getName()); // 姓名升序
})
.collect(Collectors.toList());
⚡ 排序性能考虑:
// 排序后使用limit()获取前N名 - 性能优化
List<Student> top3Students = students.stream()
.sorted(Comparator.comparing(Student::getScore).reversed())
.limit(3) // 只取前3名,减少排序开销
.collect(Collectors.toList());
// 注意:在并行Stream中使用sorted()可能影响性能
List<Student> sortedResult = students.parallelStream()
.sorted(Comparator.comparing(Student::getScore))
.collect(Collectors.toList()); // 并行排序开销较大
💡 实际应用场景:
- 排行榜:按成绩、分数等排序
- 数据展示:按时间、字母顺序展示
- 优先级处理:按重要性、紧急程度排序
- 数据分析:有序的数据处理
提取:limit()
📋 方法签名
Stream<T> limit(long maxSize)
参数说明:
- long maxSize - 要提取的最大元素数量,不能为负数
返回值:
Stream<T> - 截取后的新Stream,最多包含maxSize个元素
💡 使用提示:
limit是中间操作,是短路操作。通常用于分页处理、获取前N个元素、限制处理数据量等场景。在并行Stream中,limit的行为可能不保证顺序。
limit(n) 用于截取 Stream 中的前 n 个元素。
✂️ limit() - 截取前 N 个
// 传统写法 - 需要索引控制
List<Student> first5Students = new ArrayList<>();
for (int i = 0; i < Math.min(5, students.size()); i++) {
first5Students.add(students.get(i));
}
// Stream写法 - 简洁
List<Student> first5Students = students.stream()
.limit(5) // 只取前5个元素
.collect(Collectors.toList());
提取:skip()
📋 方法签名
Stream<T> skip(long n)
参数说明:
- long n - 要跳过的元素数量,不能为负数
返回值:
Stream<T> - 跳过后剩余元素的新Stream
💡 使用提示:
skip是中间操作,常与limit()配合实现分页功能。适用于跳过前N个元素、实现偏移量等场景。如果n大于Stream长度,返回空Stream。
skip(n) 用于跳过 Stream 中的前 n 个元素,返回剩下的元素。
⏭️ skip() - 跳过前 N 个
// 获取除了前 5 名之外的学生
List<Student> restStudents = students.stream()
.skip(5) // 跳过前5个
.collect(Collectors.toList());
// 分页操作:结合 skip + limit
int pageSize = 10;
int pageNumber = 2; // 第2页
List<Student> pageData = students.stream()
.skip((pageNumber - 1) * pageSize) // 跳过前面的元素
.limit(pageSize) // 取当前页的元素
.collect(Collectors.toList());
[T] 提取:findFirst()
📋 方法签名
Optional<T> findFirst()
参数说明:
- 无参数 - 返回Stream中的第一个元素
返回值:
Optional<T> - 包含第一个元素的Optional,如果Stream为空则返回Optional.empty()
💡 使用提示:
findFirst是终端操作和短路操作。在并行Stream中总是返回第一个元素(按顺序),具有确定性。常用于查找符合条件的第一个元素。
findFirst() 返回 Stream 中的第一个元素(包装在 Optional 中)。
🥇 findFirst() - 查找第一个
// 传统写法 - 需要循环和 break
Student firstAdult = null;
for (Student student : students) {
if (student.getAge() >= 18) {
firstAdult = student;
break;
}
}
// Stream写法 - 优雅且语义清晰
Optional<Student> firstAdult = students.stream()
.filter(student -> student.getAge() >= 18)
.findFirst();
// 处理结果
String name = firstAdult
.map(Student::getName)
.orElse("未找到成年学生");
[T] 提取:findAny()
📋 方法签名
Optional<T> findAny()
参数说明:
- 无参数 - 返回Stream中的任意一个元素
返回值:
Optional<T> - 包含任意一个元素的Optional,如果Stream为空则返回Optional.empty()
💡 使用提示:
findAny是终端操作和短路操作。在并行Stream中性能可能更好,但不保证返回哪个元素。当不关心具体返回哪个元素,只需要任意一个时使用。
findAny() 返回 Stream 中的任意一个元素(包装在 Optional 中)。
🎲 findAny() - 查找任意一个
// findAny() - 在并行 Stream (parallelStream) 中性能更好
// 因为它不需要等待"第一个"元素,只要有线程找到符合条件的就返回
Optional<Student> anyAdult = students.parallelStream()
.filter(student -> student.getAge() >= 18)
.findAny();
// 配合短路操作 anyMatch (返回 boolean)
boolean hasHighScorer = students.stream()
.anyMatch(student -> student.getScore() > 95);
findFirst():追求顺序确定性,适合有序流。findAny():追求性能,适合并行流 (Parallel Stream),不关心具体是哪一个。
[T] 匹配:anyMatch/allMatch/noneMatch()
📋 方法签名
boolean anyMatch(Predicate<? super T> predicate)
boolean allMatch(Predicate<? super T> predicate)
boolean noneMatch(Predicate<? super T> predicate)
参数说明:
- Predicate<? super T> predicate - 断言条件,用于判断元素是否满足条件
返回值:
boolean - 匹配结果:anyMatch(存在满足条件的元素)、allMatch(所有元素都满足条件)、noneMatch(没有元素满足条件)
💡 使用提示:
这些都是终端操作和短路操作。anyMatch找到第一个满足条件的就返回true;allMatch遇到第一个不满足的就返回false;noneMatch遇到第一个满足的就返回false。适合用于验证和检查场景。
匹配操作用于检查Stream中的元素是否满足指定条件,返回布尔值,是重要的终端操作。
🔍 anyMatch() - 存在匹配元素
// 传统写法 - 需要循环和标志位
boolean hasAdult = false;
for (Student student : students) {
if (student.getAge() >= 18) {
hasAdult = true;
break; // 找到一个就够了
}
}
// Stream写法 - 简洁的短路操作
boolean hasAdult = students.stream()
.anyMatch(student -> student.getAge() >= 18);
// 更多anyMatch应用
boolean hasComputerMajor = students.stream()
.anyMatch(s -> "计算机科学".equals(s.getMajor()));
boolean hasHighScore = students.stream()
.anyMatch(s -> s.getScore() > 95);
✅ allMatch() - 全部匹配
// 传统写法 - 需要检查每个元素
boolean allPassed = true;
for (Student student : students) {
if (student.getScore() < 60) {
allPassed = false;
break; // 发现一个不满足的就停止
}
}
// Stream写法 - 优雅的验证
boolean allPassed = students.stream()
.allMatch(student -> student.getScore() >= 60);
// 更多allMatch应用
boolean allHaveValidEmail = students.stream()
.allMatch(s -> s.getEmail() != null && s.getEmail().contains("@"));
boolean allAdults = students.stream()
.allMatch(s -> s.getAge() >= 18);
❌ noneMatch() - 全部不匹配
// 传统写法 - 检查是否没有满足条件的元素
boolean noneFailed = true;
for (Student student : students) {
if (student.getScore() < 60) {
noneFailed = false;
break;
}
}
// Stream写法 - 直观的表达
boolean noneFailed = students.stream()
.noneMatch(student -> student.getScore() < 60);
// 更多noneMatch应用
boolean noneUnderAge = students.stream()
.noneMatch(s -> s.getAge() < 16);
boolean noneHaveInvalidName = students.stream()
.noneMatch(s -> s.getName() == null || s.getName().trim().isEmpty());
🔄 匹配操作的组合使用
// 场景:数据验证
public boolean validateStudentData(List<Student> students) {
// 检查是否有学生数据
if (students.isEmpty()) {
return false;
}
// 检查基本要求
boolean hasStudents = students.stream().anyMatch(Objects::nonNull);
boolean allHaveNames = students.stream().allMatch(s -> s != null && s.getName() != null);
boolean noneHaveNegativeAge = students.stream().noneMatch(s -> s != null && s.getAge() < 0);
return hasStudents && allHaveNames && noneHaveNegativeAge;
}
// 场景:权限检查
public boolean canUserAccessSpecialContent(User user) {
return user != null && (
user.getRoles().stream().anyMatch(role -> "ADMIN".equals(role)) ||
(user.getRoles().stream().allMatch(role -> role.startsWith("VIP_")) &&
user.getRoles().stream().noneMatch(role -> "BANNED".equals(role)))
);
}
⚡ 性能特点:短路操作
// 匹配操作都是短路的,找到结果立即返回
// anyMatch: 找到第一个匹配的就返回true
boolean result1 = students.stream()
.anyMatch(s -> {
System.out.println("检查: " + s.getName());
return s.getScore() > 95;
});
// 只会输出到找到第一个95分以上的学生
// allMatch: 找到第一个不匹配的就返回false
boolean result2 = students.stream()
.allMatch(s -> {
System.out.println("检查: " + s.getName());
return s.getScore() >= 60;
});
// 只会输出到找到第一个不及格的学生
// noneMatch: 找到第一个匹配的就返回false
boolean result3 = students.stream()
.noneMatch(s -> {
System.out.println("检查: " + s.getName());
return s.getAge() < 16;
});
// 只会输出到找到第一个未满16岁的学生
- anyMatch:检查"是否存在",配合并行流性能更好
- allMatch:检查"全部满足",适合数据验证
- noneMatch:检查"全部不满足",比 !anyMatch 更直观
- 短路特性:不需要处理所有元素,性能高效
🎯 实际应用场景
// 数据质量检查
public boolean isDataValid(List<Transaction> transactions) {
return transactions.stream()
.allMatch(t -> t.getId() != null &&
t.getAmount() > 0 &&
t.getTimestamp() != null);
}
// 业务规则验证
public boolean canApproveLoan(Application app) {
return app.getCreditScore() > 650 &&
app.getIncome() > 50000 &&
app.getDebts().stream().noneMatch(Debt::isOverdue);
}
// 配置检查
public boolean isConfigValid(Map<String, String> config) {
List<String> requiredKeys = List.of("host", "port", "username", "password");
return requiredKeys.stream().allMatch(config::containsKey) &&
config.values().stream().noneMatch(String::isEmpty);
}
匹配操作提供了简洁高效的条件检查方式,短路特性让它们在大数据量处理时性能优异!
[T] 统计:count()
📋 方法签名
long count()
参数说明:
- 无参数 - 返回Stream中的元素数量
返回值:
long - Stream中的元素总数,如果Stream为空则返回0
💡 使用提示:
count是终端操作,会遍历整个Stream来计算元素数量。对于大型Stream或无限Stream,可能需要配合limit()等短路操作使用。在并行Stream中性能更好。
count() 是一个终结操作,用于返回 Stream 中的元素总数。
🔢 count() - 计数操作
// 传统写法 - 需要计数器
int adultCount = 0;
for (Student student : students) {
if (student.getAge() >= 18) {
adultCount++;
}
}
// Stream写法 - 简洁
long adultCount = students.stream()
.filter(student -> student.getAge() >= 18)
.count();
// 分组统计:配合 Collectors.counting()
Map<String, Long> countByGender = students.stream()
.collect(Collectors.groupingBy(
Student::getGender,
Collectors.counting()
));
count() 返回的是 long 类型,不是 int。
[T] 统计:min() & max()
📋 方法签名
Optional<T> min(Comparator<? super T> comparator)
Optional<T> max(Comparator<? super T> comparator)
参数说明:
- Comparator<? super T> comparator - 比较器,用于定义元素的比较规则
返回值:
Optional<T> - 包含最小/最大值的Optional,如果Stream为空则返回Optional.empty()
💡 使用提示:
min/max是终端操作,需要提供比较器。对于数值类型,建议使用专门的数值Stream操作。如果元素已经实现Comparable接口,可以使用Comparator.naturalOrder()。
min() 和 max() 根据指定的比较器,返回 Stream 中的最小值或最大值(包装在 Optional 中)。
📉 min() - 查找最小值
// 查找年龄最小的学生
Optional<Student> youngestStudent = students.stream()
.min(Comparator.comparing(Student::getAge));
// 获取最小年龄数值 (IntStream)
// mapToInt 将 Stream 转换为 IntStream
int minAge = students.stream()
.mapToInt(Student::getAge)
.min()
.orElse(0); // 如果流为空,返回默认值 0
mapToInt/mapToDouble 等转为基本类型流后,min() 会返回 OptionalInt 等特化类型。
[T] 统计:max()
📋 方法签名
Optional<T> max(Comparator<? super T> comparator)
参数说明:
- Comparator<? super T> comparator - 比较器,用于定义元素的比较规则
返回值:
Optional<T> - 包含最大值的Optional,如果Stream为空则返回Optional.empty()
💡 使用提示:
max是终端操作,需要提供比较器。对于数值类型,建议使用专门的数值Stream操作。如果元素已经实现Comparable接口,可以使用Comparator.naturalOrder()。
max() 根据指定的比较器,返回 Stream 中的最大值(包装在 Optional 中)。
📈 max() - 查找最大值
// 查找分数最高的学生
Optional<Student> topStudent = students.stream()
.max(Comparator.comparing(Student::getScore));
// 复杂场景:查找"大四"学生中分数最高的
Optional<Student> bestSenior = students.stream()
.filter(s -> "Senior".equals(s.getGrade()))
.max(Comparator.comparing(Student::getScore));
[T] 统计:sum()
📋 方法签名
int sum() // IntStream
long sum() // LongStream
double sum() // DoubleStream
参数说明:
- 无参数 - 仅适用于数值Stream (IntStream, LongStream, DoubleStream)
返回值:
int/long/double - Stream中所有元素的和,空Stream返回对应类型的默认值
💡 使用提示:
sum是终端操作,专用于数值Stream。需要通过mapToInt()、mapToLong()、mapToDouble()将对象Stream转换为数值Stream。对于大数值计算要注意溢出问题。
sum() 用于对数值流(IntStream, LongStream, DoubleStream)中的元素求和。
➕ sum() - 求和计算
// 传统写法
double totalScore = 0;
for (Student s : students) {
totalScore += s.getScore();
}
// Stream写法
// 注意:sum() 仅在基本类型流(IntStream等)中可用
double totalScore = students.stream()
.mapToDouble(Student::getScore)
.sum();
// 复杂求和:计算所有商品的折后总价
double totalValue = products.stream()
.mapToDouble(p -> p.getPrice() * p.getDiscount())
.sum();
Stream<T> 没有 sum() 方法,必须先通过 mapToInt/mapToLong/mapToDouble 转换。
[T] 统计:average()
📋 方法签名
OptionalDouble average() // IntStream, LongStream
OptionalDouble average() // DoubleStream
参数说明:
- 无参数 - 仅适用于数值Stream (IntStream, LongStream, DoubleStream)
返回值:
OptionalDouble - 包含平均值的OptionalDouble,空Stream返回OptionalDouble.empty()
💡 使用提示:
average是终端操作,专用于数值Stream。返回OptionalDouble避免空Stream异常。可以配合orElse()提供默认值。对于DoubleStream要注意NaN和无穷值的处理。
average() 用于计算数值流的平均值,结果包装在 OptionalDouble 中。
➗ average() - 计算平均值
// 计算平均分
double averageScore = students.stream()
.mapToDouble(Student::getScore)
.average() // 返回 OptionalDouble
.orElse(0.0); // 处理空流的情况
// 综合统计信息 (一次性获取 count, sum, min, max, average)
IntSummaryStatistics stats = students.stream()
.mapToInt(Student::getAge)
.summaryStatistics();
System.out.println("平均年龄: " + stats.getAverage());
System.out.println("最大年龄: " + stats.getMax());
System.out.println("总人数: " + stats.getCount());
summaryStatistics(),只需遍历一次流,性能更优!
[T] 规约:reduce()
📋 方法签名
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
参数说明:
- T identity - 初始值,用于第一次累加操作
- BinaryOperator<T> accumulator - 累加器函数,定义如何组合两个元素
- BiFunction<U, ? super T, U> accumulator - 转换累加器,用于类型转换场景
- BinaryOperator<U> combiner - 合并器函数,用于并行Stream的结果合并
返回值:
Optional<T>/T/U - 规约结果:无初始值时返回Optional,有初始值时返回具体类型
💡 使用提示:
reduce是最灵活的终端操作,可用于求和、求积、字符串拼接等任意聚合操作。有初始值的版本更安全。在并行Stream中,combiner函数用于合并各线程的局部结果。
reduce() 操作用于将Stream中的元素按照指定的规则"规约"成一个值,是最灵活的聚合操作。
🔄 基础reduce() - 求和示例
// 传统写法 - 显式累加
double totalScore = 0;
for (Student student : students) {
totalScore += student.getScore();
}
// Stream写法 - reduce()规约
Optional<Double> totalScore = students.stream()
.map(Student::getScore)
.reduce((score1, score2) -> score1 + score2);
// 带初始值的reduce() - 避免Optional
double totalScoreWithInit = students.stream()
.map(Student::getScore)
.reduce(0.0, Double::sum);
// 使用方法引用更简洁
double totalScoreMethodRef = students.stream()
.map(Student::getScore)
.reduce(0.0, Double::sum);
⚙️ 自定义规约逻辑
// 找出最高分的学生
Optional<Student> topStudent = students.stream()
.reduce((s1, s2) -> s1.getScore() > s2.getScore() ? s1 : s2);
// 字符串拼接 - 传统方式
String allNames1 = students.stream()
.map(Student::getName)
.reduce("", (name1, name2) -> name1 + ", " + name2);
// 字符串拼接 - 更优雅的方式
String allNames2 = students.stream()
.map(Student::getName)
.collect(Collectors.joining(", "));
// 复杂对象规约 - 计算加权平均
WeightedAverageResult weightedAvg = students.stream()
.reduce(
new WeightedAverageResult(0.0, 0.0), // 初始值
(result, student) -> new WeightedAverageResult(
result.totalScore + student.getScore() * student.getCredit(),
result.totalCredit + student.getCredit()
),
(result1, result2) -> new WeightedAverageResult(
result1.totalScore + result2.totalScore,
result1.totalCredit + result2.totalCredit
)
);
🎯 reduce() vs collect() 的选择
// reduce() - 返回单一值,适合聚合操作
Integer maxValue = numbers.stream()
.reduce(Integer::max)
.orElse(0);
// collect() - 返回集合,适合收集操作
List<Integer> positiveNumbers = numbers.stream()
.filter(n -> n > 0)
.collect(Collectors.toList());
// reduce() - 自定义聚合逻辑
String concatenated = words.stream()
.reduce("", String::concat);
// collect() - 标准收集操作
List<String> wordList = words.stream()
.collect(Collectors.toList());
⚡ 性能考虑:
- 并行安全:reduce()需要满足结合律才能安全并行执行
- 初始值选择:带初始值的reduce()可以避免Optional处理
- 身份元素:初始值应该是操作的身份元素(如加法的0、乘法的1)
- 性能优化:对于简单聚合,优先使用专门的统计方法(sum、count等)
reduce() 是Stream中最强大的聚合操作,可以实现任何自定义的聚合逻辑!
[T] 收集:collect()
📋 方法签名
<R, A> R collect(Collector<? super T, A, R> collector)
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
参数说明:
- Collector<? super T, A, R> collector - 收集器,定义如何收集元素到结果容器中
- Supplier<R> supplier - 结果容器的工厂方法
- BiConsumer<R, ? super T> accumulator - 累加器,定义如何将元素添加到容器中
- BiConsumer<R, R> combiner - 合并器,用于并行Stream时合并两个容器
返回值:
R - 收集结果,可以是List、Set、Map或任意自定义类型
💡 使用提示:
collect是最灵活的终端操作,Collectors类提供了丰富的预定义收集器。常用场景:toList()、toSet()、toMap()、groupingBy()、partitioningBy()等。自定义收集器可以实现复杂的数据转换需求。
collect() 是Stream的终端操作,用于将Stream中的元素收集到容器中,是最灵活的结果收集方式。
📦 基础收集操作
// 收集到List
List<Student> studentList = students.stream()
.filter(s -> s.getAge() >= 18)
.collect(Collectors.toList());
// 收集到Set(自动去重)
Set<String> uniqueNames = students.stream()
.map(Student::getName)
.collect(Collectors.toSet());
// 收集到具体集合类型
ArrayList<Student> arrayList = students.stream()
.filter(s -> s.getScore() > 80)
.collect(Collectors.toCollection(ArrayList::new));
// 收集到Map - 简单映射
Map<Integer, Student> studentMap = students.stream()
.collect(Collectors.toMap(
Student::getId, // key mapper
student -> student // value mapper
));
// 收集到Map - 处理重复键
Map<String, Student> nameToStudent = students.stream()
.collect(Collectors.toMap(
Student::getName, // key mapper
student -> student, // value mapper
(existing, replacement) -> existing // 合并函数:保留现有值
));
📊 分组操作 - groupingBy()
// 按专业分组
Map<String, List<Student>> studentsByMajor = students.stream()
.collect(Collectors.groupingBy(Student::getMajor));
// 分组并统计每组数量
Map<String, Long> countByMajor = students.stream()
.collect(Collectors.groupingBy(
Student::getMajor,
Collectors.counting()
));
// 分组并计算平均成绩
Map<String, Double> avgScoreByMajor = students.stream()
.collect(Collectors.groupingBy(
Student::getMajor,
Collectors.averagingDouble(Student::getScore)
));
// 多级分组
Map<String, Map<String, List<Student>>> multiLevelGroup = students.stream()
.collect(Collectors.groupingBy(
Student::getMajor, // 一级分组:专业
Collectors.groupingBy(student -> { // 二级分组:年龄段
int age = student.getAge();
if (age < 18) return "未成年";
else if (age < 25) return "青年";
else return "成年";
})
));
✂️ 分区操作 - partitioningBy()
// 按是否成年分区(只返回true/false两个组)
Map<Boolean, List<Student>> byAdult = students.stream()
.collect(Collectors.partitioningBy(
student -> student.getAge() >= 18
));
// 分区并进行进一步收集
Map<Boolean, String> namesByAdult = students.stream()
.collect(Collectors.partitioningBy(
student -> student.getAge() >= 18,
Collectors.mapping(
Student::getName,
Collectors.joining(", ")
)
));
🎯 高级收集操作
// 收集统计信息
IntSummaryStatistics ageStats = students.stream()
.collect(Collectors.summarizingInt(Student::getAge));
// 按条件收集(类似SQL的CASE WHEN)
Map<String, List<Student>> scoreCategories = students.stream()
.collect(Collectors.groupingBy(student -> {
double score = student.getScore();
if (score >= 90) return "优秀";
else if (score >= 80) return "良好";
else if (score >= 70) return "中等";
else if (score >= 60) return "及格";
else return "不及格";
}));
// 自定义收集器
String result = students.stream()
.collect(CustomCollector.toCustomString());
💡 实际应用场景:
- 数据报表:分组统计、汇总计算
- 数据转换:List到Map、Set等容器转换
- 数据分析:按多维度分类统计
- 结果封装:将Stream结果转换为业务对象
[T] 数组转换:toArray()
📋 方法签名
Object[] toArray()
<A> A[] toArray(IntFunction<A[]> generator)
参数说明:
- IntFunction<A[]> generator - 数组工厂函数,根据数组长度创建目标类型的数组
返回值:
Object[] / A[] - 包含Stream所有元素的数组
💡 使用提示:
toArray是终端操作。无参版本返回Object[],需要强制类型转换;有参版本可返回指定类型的数组,更安全。适用于需要与数组API交互、性能优化等场景。
toArray() 是一个终端操作,用于将Stream转换为数组。这在需要与使用数组的老代码集成时特别有用。
🔄 基础toArray() - 转换为Object数组
// 传统写法 - 需要手动创建数组
List<Student> students = getStudents();
Student[] array = new Student[students.size()];
for (int i = 0; i < students.size(); i++) {
array[i] = students.get(i);
}
// Stream写法 - 简洁转换
Object[] objectArray = students.stream()
.toArray(); // 返回 Object[]
// 注意:直接调用toArray()返回的是Object[]类型
Object[] names = students.stream()
.map(Student::getName)
.toArray();
// 结果:Object[] containing String objects
🎯 指定类型数组 - toArray(IntFunction)
// 转换为指定类型的数组
Student[] studentArray = students.stream()
.toArray(Student[]::new);
// 传统方式需要额外转换
// Object[] objArray = students.stream().toArray();
// Student[] studentArray = Arrays.copyOf(objArray, objArray.length, Student[].class);
// 字符串数组示例
String[] nameArray = students.stream()
.map(Student::getName)
.toArray(String[]::new);
// 基本类型数组需要通过基本类型Stream
int[] ageArray = students.stream()
.mapToInt(Student::getAge)
.toArray(); // IntStream.toArray() 返回 int[]
double[] scoreArray = students.stream()
.mapToDouble(Student::getScore)
.toArray(); // DoubleStream.toArray() 返回 double[]
💡 实际应用场景
// 场景1:与数组API集成
// 某些老API只接受数组参数
Arrays.sort(students.stream()
.filter(Student::isActive)
.toArray(Student[]::new));
// 场景2:可变参数方法调用
public void processStudents(Student... students) {
// 处理学生数组
}
// 调用方法
processStudents(students.stream()
.filter(s -> s.getScore() >= 80)
.toArray(Student[]::new));
// 场景3:反射调用
Method method = clazz.getMethod("setStudents", Student[].class);
method.invoke(obj, students.stream()
.toArray(Student[]::new));
⚡ 性能考虑
// 性能对比:toArray vs ArrayList.toArray
List<Student> students = getLargeStudentList();
// 方式1:先collect再toArray
long start1 = System.currentTimeMillis();
Student[] array1 = students.stream()
.filter(Student::isActive)
.collect(Collectors.toList())
.toArray(new Student[0]);
long time1 = System.currentTimeMillis() - start1;
// 方式2:直接toArray
long start2 = System.currentTimeMillis();
Student[] array2 = students.stream()
.filter(Student::isActive)
.toArray(Student[]::new);
long time2 = System.currentTimeMillis() - start2;
// 方式3:toArray(长度为0的数组) - JVM优化
Student[] array3 = students.stream()
.filter(Student::isActive)
.toArray(Student[]::new); // 推荐:JVM会优化数组大小
// 方式4:toArray(预分配大小) - 适用于知道确切大小
List<Student> filtered = students.stream()
.filter(Student::isActive)
.collect(Collectors.toList());
Student[] array4 = filtered.toArray(new Student[filtered.size()]);
🔧 高级用法
// 复杂数组转换
// 场景:将学生转换为学生DTO数组
StudentDTO[] dtoArray = students.stream()
.map(student -> {
StudentDTO dto = new StudentDTO();
dto.setName(student.getName());
dto.setAge(student.getAge());
dto.setScore(student.getScore());
return dto;
})
.toArray(StudentDTO[]::new);
// 多维数组创建
// 创建学生姓名和成绩的二维数组
String[][] nameScorePairs = students.stream()
.map(student -> new String[]{
student.getName(),
String.valueOf(student.getScore())
})
.toArray(String[][]::new);
// 数组与流的相互转换
public static <T> Stream<T> arrayToStream(T[] array) {
return Arrays.stream(array);
}
public static <T> T[] streamToArray(Stream<T> stream, Class<T> clazz) {
return stream.toArray(size -> (T[]) Array.newInstance(clazz, size));
}
⚠️ 注意事项和最佳实践
// ✅ 推荐:使用方法引用指定数组类型
String[] names = nameList.stream()
.toArray(String[]::new);
// ❌ 避免:直接使用toArray()后强制转换
Object[] objects = nameList.stream().toArray();
String[] names2 = (String[]) objects; // 可能抛出ClassCastException
// ✅ 推荐:基本类型使用专门的方法
int[] ages = students.stream()
.mapToInt(Student::getAge)
.toArray();
// ✅ 推荐:已知大小时可以优化
List<Student> filtered = students.stream()
.filter(Student::isActive)
.collect(Collectors.toList());
Student[] array = filtered.toArray(new Student[filtered.size()]);
// 💡 小技巧:空数组的处理
Student[] array = students.stream()
.filter(Student::isActive)
.toArray(Student[]::new);
// 如果没有元素,会返回长度为0的数组,不会返回null
- 类型安全:使用
toArray(T[]::new)而非强制转换 - 基本类型:使用
mapToInt等方法转换为基本类型流 - 性能:直接调用
toArray()比先collect再转换更高效 - 空数组:
toArray()永远返回数组(可能是空数组),从不返回null
toArray() 提供了Stream到数组的无缝转换,是连接函数式编程和传统数组编程的桥梁!
组合:concat()
📋 方法签名
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
参数说明:
- Stream<? extends T> a - 第一个要合并的Stream
- Stream<? extends T> b - 第二个要合并的Stream
返回值:
Stream<T> - 包含两个Stream所有元素的新Stream,保持原始顺序
💡 使用提示:
concat是静态方法,用于懒连接两个Stream。连接后的Stream会包含所有元素,先处理第一个Stream的所有元素,再处理第二个Stream。对于大型Stream,可能影响性能。
Stream的组合操作可以将多个流合并为一个流,方便统一处理。
🔗 concat() - 流合并操作
// 传统写法 - 需要手动合并集合
List<Student> allStudents = new ArrayList<>(undergraduates);
allStudents.addAll(graduateStudents);
allStudents.addAll(exchangeStudents);
// Stream写法 - 流合并
Stream<Student> allStudentStream = Stream.concat(
Stream.concat(undergraduates.stream(), graduateStudents.stream()),
exchangeStudents.stream()
);
// 多个流合并
Stream<Student> mergedStream = Stream.of(
undergraduates.stream(),
graduateStudents.stream(),
exchangeStudents.stream()
)
.flatMap(Function.identity()); // 扁平化合并
// 合并后进行统一处理
List<String> allStudentNames = Stream.concat(
undergraduates.stream(),
graduateStudents.stream()
)
.filter(student -> student.isActive()) // 统一筛选
.map(Student::getName) // 统一映射
.collect(Collectors.toList()); // 统一收集
Stream.concat 是惰性求值的,只有在终端操作执行时才会真正合并。
并行操作:parallel()
📋 方法签名
Stream<T> parallel()
Stream<T> sequential()
boolean isParallel()
参数说明:
- 无参数 - parallel()转换为并行Stream,sequential()转换回顺序Stream,isParallel()检查是否并行
返回值:
Stream<T>/boolean - parallel/sequential返回新的Stream,isParallel返回当前并行状态
💡 使用提示:
parallel()适合CPU密集型操作和大数据集。要求数据源可分割、操作无状态、无依赖。并行操作可能改变元素处理顺序,可能影响排序和有序性。
Stream API 提供了极其简单的并行处理能力,充分利用多核 CPU 提升性能。
parallel() - 并行处理
默认并行路数 :通常等于 Runtime.getRuntime().availableProcessors() - 1
例如,在8核心CPU上,默认并行路数为7。
// 串行处理(默认)
List<Student> result1 = students.stream()
.filter(s -> s.getAge() >= 18)
.collect(Collectors.toList());
// 并行处理 - 一行代码实现
List<Student> result2 = students.parallelStream() // 直接创建并行流
.filter(s -> s.getAge() >= 18)
.collect(Collectors.toList());
// 或者使用parallel()方法转换
List<Student> result3 = students.stream()
.parallel() // 转换为并行流
.filter(s -> s.getAge() >= 18)
.collect(Collectors.toList());
// 串行和并行混合使用
students.stream()
.filter(s -> s.getMajor().equals("计算机科学")) // 串行:简单筛选
.parallel() // 并行:复杂计算
.map(this::calculateComplexScore) // 并行计算
.sequential() // 串行:有序收集
.sorted(Comparator.comparing(Student::getScore))
.collect(Collectors.toList());
🚀 并行处理性能优化
// 性能测试示例
List<Integer> largeList = IntStream.range(0, 1_000_000)
.boxed()
.collect(Collectors.toList());
// 串行处理时间
long start = System.currentTimeMillis();
long sum1 = largeList.stream()
.mapToLong(Integer::longValue)
.filter(n -> n % 2 == 0)
.sum();
long serialTime = System.currentTimeMillis() - start;
// 并行处理时间
start = System.currentTimeMillis();
long sum2 = largeList.parallelStream()
.mapToLong(Integer::longValue)
.filter(n -> n % 2 == 0)
.sum();
long parallelTime = System.currentTimeMillis() - start;
System.out.println("串行时间: " + serialTime + "ms");
System.out.println("并行时间: " + parallelTime + "ms");
System.out.println("加速比: " + (double) serialTime / parallelTime);
⚠️ 并行处理注意事项:
// 适合并行处理的场景
List<ComplexData> result = largeDataSet.parallelStream()
.filter(data -> data.isValid()) // 无状态过滤
.map(this::expensiveComputation) // CPU密集型操作
.collect(Collectors.toList());
// 不适合并行处理的场景
List<Integer> result2 = smallList.stream() // 数据量小
.sorted() // 顺序敏感操作
.limit(5) // 短路操作
.collect(Collectors.toList());
// 线程安全问题示例
List<StringBuilder> unsafeResult = students.parallelStream()
.collect(ArrayList::new, // 这里有线程安全问题!
(list, student) -> list.add(new StringBuilder(student.getName())),
List::addAll);
// 正确的线程安全写法
List<String> safeResult = students.parallelStream()
.map(Student::getName)
.collect(Collectors.toList()); // 使用线程安全的收集器
💡 最佳实践建议:
- 数据量判断:通常超过10000个元素才考虑并行
- 操作类型:CPU密集型操作适合并行,IO密集型不适合
- 无状态操作:确保操作函数是无状态的
- 性能测试:实际测试并行是否带来性能提升
- 避免过度并行:嵌套并行流会降低性能
并行优化:unordered()
📋 方法签名
Stream<T> unordered()
参数说明:
- 无参数 - 移除Stream的有序约束,允许并行处理时不保持原始顺序
返回值:
Stream<T> - 无序的新Stream,在并行处理中可能提升性能
💡 使用提示:
unordered()是中间操作,主要用于并行Stream的性能优化。当结果顺序不重要时使用,可以减少同步开销。对于distinct()、groupingBy()等操作,有序约束可能带来性能损失。
unordered() 是一个中间操作,用于移除Stream的有序约束,在并行处理中可以显著提升性能。
🚀 unordered() - 移除有序约束
// 有序流 - 并行处理时需要维护顺序
List<Student> result1 = students.parallelStream()
.filter(s -> s.getAge() >= 18)
.collect(Collectors.toList());
// 结果保持原始顺序,但并行效率较低
// 无序流 - 并行处理时不需要维护顺序
List<Student> result2 = students.parallelStream()
.unordered() // 移除顺序约束
.filter(s -> s.getAge() >= 18)
.collect(Collectors.toList());
// 结果顺序可能不同,但并行效率更高
⚡ 性能对比示例
// 性能测试:有序 vs 无序并行流
List<Integer> largeList = IntStream.range(0, 1_000_000)
.boxed()
.collect(Collectors.toList());
// 有序并行处理
long start1 = System.currentTimeMillis();
long sum1 = largeList.parallelStream()
.filter(n -> n % 2 == 0)
.mapToLong(n -> n * n)
.sum();
long orderedTime = System.currentTimeMillis() - start1;
// 无序并行处理
long start2 = System.currentTimeMillis();
long sum2 = largeList.parallelStream()
.unordered() // 移除顺序约束
.filter(n -> n % 2 == 0)
.mapToLong(n -> n * n)
.sum();
long unorderedTime = System.currentTimeMillis() - start2;
System.out.println("有序并行: " + orderedTime + "ms");
System.out.println("无序并行: " + unorderedTime + "ms");
System.out.println("性能提升: " + (double) orderedTime / unorderedTime + "x");
// 通常无序并行会快 20-50%
💡 何时使用 unordered()
// ✅ 适合使用 unordered 的场景
List<Student> highScoreStudents = students.parallelStream()
.unordered() // 不关心顺序
.filter(s -> s.getScore() > 90)
.collect(Collectors.toList());
// ✅ 后续有排序操作,可以提前使用 unordered
List<Student> topStudents = students.parallelStream()
.unordered() // 移除原始顺序约束
.filter(s -> s.getAge() >= 18)
.sorted(Comparator.comparing(Student::getScore)) // 重新排序
.limit(10)
.collect(Collectors.toList());
// ✅ 聚合操作,顺序不重要
double averageScore = students.parallelStream()
.unordered()
.filter(Student::isActive)
.mapToDouble(Student::getScore)
.average()
.orElse(0.0);
// ❌ 需要保持原始顺序时不要使用
List<String> orderedNames = students.stream() // 使用串行流
.filter(s -> s.getAge() >= 18)
.map(Student::getName)
.collect(Collectors.toList());
🎯 unordered() 与其他操作的配合
// 1. unordered + distinct() - 无序去重更快
Set<String> uniqueNames = students.parallelStream()
.unordered()
.map(Student::getName)
.distinct()
.collect(Collectors.toSet());
// 2. unordered + groupBy() - 分组操作不关心顺序
Map<String, List<Student>> byMajor = students.parallelStream()
.unordered()
.collect(Collectors.groupingBy(Student::getMajor));
// 3. unordered + limit() - 非顺序敏感的截取
List<Student> sample = students.parallelStream()
.unordered()
.filter(Student::isActive)
.limit(100) // 任意100个活跃学生
.collect(Collectors.toList());
// 4. unordered + findAny() - 自然配合
Optional<Student> anyAdult = students.parallelStream()
.unordered() // findAny 本身就不保证顺序
.filter(s -> s.getAge() >= 18)
.findAny();
📊 不同数据源的影响
// List - 有序集合,unordered 效果明显
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.parallelStream()
.unordered() // 移除 List 的顺序约束
.map(n -> n * 2)
.collect(Collectors.toList());
// Set - 无序集合,unordered 没有额外效果
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
set.parallelStream()
.unordered() // Set 本来就是无序的,无额外效果
.map(n -> n * 2)
.collect(Collectors.toList());
// 数组 - 有序,unordered 有效果
String[] array = {"a", "b", "c", "d", "e"};
Arrays.stream(array)
.parallel()
.unordered() // 移除数组的顺序约束
.map(String::toUpperCase)
.collect(Collectors.toList());
🔧 实际优化案例
// 案例1:大数据过滤优化
public List<Transaction> filterValidTransactions(List<Transaction> transactions) {
return transactions.parallelStream()
.unordered() // 顺序不重要,性能优先
.filter(t -> t.getAmount() > 0)
.filter(t -> t.getTimestamp() != null)
.filter(t -> t.isValid())
.collect(Collectors.toList());
}
// 案例2:统计计算优化
public Statistics calculateOrderStatistics(List<Order> orders) {
return orders.parallelStream()
.unordered() // 统计计算不需要顺序
.filter(Order::isCompleted)
.collect(Collectors.collectingAndThen(
Collectors.toList(),
list -> {
double sum = list.stream().mapToDouble(Order::getAmount).sum();
double avg = sum / list.size();
double max = list.stream().mapToDouble(Order::getAmount).max().orElse(0);
return new Statistics(list.size(), sum, avg, max);
}
));
}
// 案例3:数据转换优化
public List<ProductDTO> convertProducts(List<Product> products) {
return products.parallelStream()
.unordered() // 转换后的顺序不重要
.filter(Product::isActive)
.map(product -> {
ProductDTO dto = new ProductDTO();
dto.setId(product.getId());
dto.setName(product.getName());
dto.setPrice(product.getPrice());
return dto;
})
.collect(Collectors.toList());
}
- 性能优先:当顺序不重要时,优先使用
unordered() - 大数据量:数据量越大,
unordered()的性能提升越明显 - 复杂操作:对于复杂的中间操作,
unordered()效果更好 - 提前调用:在并行流开始处尽早调用
unordered()
⚠️ 注意事项
// 注意1:unordered 的影响是持久的
Stream<Integer> stream = list.stream()
.unordered() // 一旦设置为无序,后续操作都不保证顺序
.filter(n -> n > 5)
.map(n -> n * 2);
// stream 是无序的
// 注意2:某些操作会重新引入顺序
List<Integer> result = list.parallelStream()
.unordered()
.filter(n -> n > 5)
.sorted() // sorted() 会重新建立顺序
.collect(Collectors.toList());
// 注意3:limit() 在有序和无序流中的行为不同
List<Integer> orderedSample = list.parallelStream()
.limit(10) // 取前10个元素,保持顺序
.collect(Collectors.toList());
List<Integer> unorderedSample = list.parallelStream()
.unordered()
.limit(10) // 任意10个元素,顺序不确定
.collect(Collectors.toList());
unordered() 是并行流的重要优化手段,在不依赖顺序的场景下能显著提升性能!
[T] 有序遍历:forEachOrdered()
📋 方法签名
void forEachOrdered(Consumer<? super T> action)
参数说明:
- Consumer<? super T> action - 对Stream中每个元素执行的操作
返回值:
void - 无返回值,这是一个终端操作
💡 使用提示:
forEachOrdered是终端操作,在并行Stream中保持遇到顺序。比forEach()有更多同步开销,只在需要保证顺序时使用。适用于需要维持原始顺序的并行处理场景。
forEachOrdered() 是一个终端操作,用于按顺序遍历Stream中的元素。在并行流中,它能保持原始顺序。
📋 forEachOrdered() - 保证顺序遍历
// 串行流中:forEach 和 forEachOrdered 效果相同
List<String> names = Arrays.asList("张三", "李四", "王五", "赵六");
System.out.println("使用 forEach (串行流):");
names.stream()
.forEach(name -> System.out.println(name + " "));
// 输出:张三 李四 王五 赵六
System.out.println("\n使用 forEachOrdered (串行流):");
names.stream()
.forEachOrdered(name -> System.out.println(name + " "));
// 输出:张三 李四 王五 赵六
🔄 并行流中的区别
// 并行流中:forEach 顺序不确定
System.out.println("使用 forEach (并行流):");
names.parallelStream()
.forEach(name -> System.out.println(name + " "));
// 可能输出:李四 张三 赵六 王五 (顺序不保证)
// 并行流中:forEachOrdered 保证顺序
System.out.println("\n使用 forEachOrdered (并行流):");
names.parallelStream()
.forEachOrdered(name -> System.out.println(name + " "));
// 输出:张三 李四 王五 赵六 (保证原始顺序)
💡 实际应用场景
// 场景1:保持文件处理顺序
List<File> files = getFilesInOrder(); // 按时间排序的文件列表
files.parallelStream()
.forEachOrdered(file -> {
// 并行处理但保持输出顺序
String content = processFile(file);
System.out.println(file.getName() + ": " + content);
});
// 场景2:有序报表生成
List<SalesRecord> records = getSalesRecords(); // 已按日期排序
records.parallelStream()
.forEachOrdered(record -> {
// 并行计算但按日期顺序生成报表
ReportData data = calculateReportData(record);
addToReport(data);
});
// 场景3:批量数据库插入(保持顺序)
List<User> users = getUsersInPriorityOrder();
users.parallelStream()
.forEachOrdered(user -> {
// 并行验证但按优先级顺序插入
if (validateUser(user)) {
insertUser(user);
}
});
⚠️ 性能考虑
// 性能对比测试
List<Integer> largeList = IntStream.range(0, 1_000_000)
.boxed()
.collect(Collectors.toList());
// 串行 forEach
long start1 = System.currentTimeMillis();
largeList.stream()
.forEach(i -> heavyOperation(i));
long time1 = System.currentTimeMillis() - start1;
// 并行 forEach (不保证顺序)
long start2 = System.currentTimeMillis();
largeList.parallelStream()
.forEach(i -> heavyOperation(i));
long time2 = System.currentTimeMillis() - start2;
// 并行 forEachOrdered (保证顺序)
long start3 = System.currentTimeMillis();
largeList.parallelStream()
.forEachOrdered(i -> heavyOperation(i));
long time3 = System.currentTimeMillis() - start3;
System.out.println("串行 forEach: " + time1 + "ms");
System.out.println("并行 forEach: " + time2 + "ms");
System.out.println("并行 forEachOrdered: " + time3 + "ms");
// 结果:并行 forEach 最快,forEachOrdered 稍慢但仍比串行快
🎯 何时使用 forEachOrdered
// ✅ 使用 forEachOrdered 的场景
List<Order> orders = getOrdersByDate(); // 按日期排序
orders.parallelStream()
.forEachOrdered(order -> {
// 需要保持日期顺序的处理
processOrderInSequence(order);
});
// ❌ 不需要顺序时使用 forEach
List<Product> products = getAllProducts();
products.parallelStream()
.forEach(product -> {
// 不需要顺序的独立操作
updateProductCache(product);
});
// ✅ 串行流中使用 forEach (性能更好)
List<Student> students = getStudents();
students.stream()
.forEach(student -> {
// 串行流中 forEach 和 forEachOrdered 效果相同
printStudentInfo(student);
});
- 串行流:使用
forEach(),性能更好 - 并行流 + 需要顺序:使用
forEachOrdered() - 并行流 + 不需要顺序:使用
forEach(),性能最佳 - 数据库操作:如果有顺序依赖,使用
forEachOrdered()
🔄 与其他操作的区别
// forEach vs forEachOrdered vs iterator
List<String> data = Arrays.asList("A", "B", "C");
// 1. forEach - 终端操作,简洁
data.forEach(System.out::println);
// 2. forEachOrdered - 终端操作,保证顺序
data.parallelStream().forEachOrdered(System.out::println);
// 3. iterator - 中间操作,需要手动遍历
Iterator<String> it = data.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
// 4. Stream转List后再遍历
List<String> result = data.stream()
.filter(s -> s.length() > 0)
.collect(Collectors.toList());
result.forEach(System.out::println);
forEachOrdered() 在并行处理中保持顺序,适合需要有序输出的场景,但会牺牲一些性能!
资源管理:onClose()
📋 方法签名
Stream<T> onClose(Runnable closeHandler)
参数说明:
- Runnable closeHandler - Stream关闭时执行的回调处理器
返回值:
Stream<T> - 带有资源管理的新Stream
💡 使用提示:
onClose是中间操作,用于资源管理。当Stream调用close()或自动关闭时,注册的回调会被执行。适用于文件流、数据库连接等需要显式关闭的资源,支持链式注册多个回调。
onClose() 是一个中间操作,用于注册Stream关闭时的回调处理器。主要用于需要资源管理的场景,特别是文件流等IO操作。
🔧 onClose() - 注册关闭回调
// 传统资源管理 - 需要 try-with-resources
try (Stream<String> lines = Files.lines(path)) {
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
// Stream with onClose - 自定义关闭处理
Stream<String> lines = Files.lines(path)
.onClose(() -> System.out.println("文件流已关闭"));
try {
lines.forEach(System.out::println);
} finally {
lines.close(); // 会触发 onClose 回调
}
📁 文件流中的应用
// 处理日志文件示例
public void processLogFile(Path logFile) throws IOException {
try (Stream<String> lines = Files.lines(logFile)
.onClose(() -> {
System.out.println("日志文件处理完成: " + logFile);
updateProcessingStatus(logFile, "COMPLETED");
})) {
List<LogEntry> errors = lines
.filter(line -> line.contains("ERROR"))
.map(this::parseLogEntry)
.collect(Collectors.toList());
System.out.println("发现 " + errors.size() + " 个错误");
}
// 自动调用 close(),触发 onClose 回调
}
// 多个文件批量处理
public void processMultipleFiles(List<Path> files) {
files.forEach(file -> {
try {
Stream<String> stream = Files.lines(file)
.onClose(() -> {
System.out.println("处理完成: " + file.getFileName());
archiveFile(file); // 归档已处理文件
});
stream
.filter(line -> !line.startsWith("#"))
.forEach(this::processLine);
} catch (IOException e) {
System.err.println("处理文件失败: " + file);
}
});
}
🔗 链式 onClose 处理器
// 可以链式添加多个 onClose 处理器
Stream<String> lines = Files.lines(path)
.onClose(() -> System.out.println("第一个关闭处理器"))
.onClose(() -> System.out.println("第二个关闭处理器"))
.onClose(() -> {
// 清理临时文件
cleanupTempFiles();
System.out.println("临时文件已清理");
});
// 关闭时的执行顺序与注册顺序相反
try {
lines.forEach(System.out::println);
} finally {
lines.close();
}
// 输出:
// 临时文件已清理
// 第二个关闭处理器
// 第一个关闭处理器
💾 数据库连接管理
// 数据库查询的Stream包装
public Stream<Product> getProductsStream(String category) {
Connection conn = getConnection();
PreparedStatement stmt = null;
ResultSet rs = null;
try {
stmt = conn.prepareStatement("SELECT * FROM products WHERE category = ?");
stmt.setString(1, category);
rs = stmt.executeQuery();
return StreamSupport.stream(
new Spliterators.AbstractSpliterator<Product>(Long.MAX_VALUE, 0) {
@Override
public boolean tryAdvance(Consumer<? super Product> action) {
try {
if (rs.next()) {
action.accept(mapRowToProduct(rs));
return true;
}
return false;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}, false)
.onClose(() -> {
// 确保资源被正确关闭
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
System.out.println("数据库资源已释放");
} catch (SQLException e) {
System.err.println("关闭数据库资源时出错: " + e.getMessage());
}
});
} catch (SQLException e) {
// 如果创建Stream失败,需要手动关闭资源
closeQuietly(rs, stmt, conn);
throw new RuntimeException(e);
}
}
// 使用示例
public void processElectronicsProducts() {
try (Stream<Product> products = getProductsStream("Electronics")) {
products
.filter(p -> p.getPrice() > 1000)
.forEach(p -> System.out.println(p.getName() + ": $" + p.getPrice()));
}
// 自动关闭数据库连接
}
🔄 自定义资源管理
// 网络连接的资源管理
public Stream<String> fetchUrlsStream(List<String> urls) {
HttpClient client = HttpClient.newHttpClient();
return urls.stream()
.map(url -> {
try {
HttpResponse<String> response = client.send(
HttpRequest.newBuilder(URI.create(url)).build(),
HttpResponse.BodyHandlers.ofString()
);
return response.body();
} catch (Exception e) {
return "Error fetching " + url + ": " + e.getMessage();
}
})
.onClose(() -> {
// 关闭HTTP客户端(如果需要)
System.out.println("HTTP请求完成");
});
}
// 临时文件管理
public Stream<String> processTempFiles() {
List<Path> tempFiles = new ArrayList<>();
try {
// 创建临时文件
for (int i = 0; i < 5; i++) {
Path tempFile = Files.createTempFile("process", ".txt");
tempFiles.add(tempFile);
Files.write(tempFile, ("Content " + i).getBytes());
}
return tempFiles.stream()
.map(path -> {
try {
return Files.readString(path);
} catch (IOException e) {
return "Error reading file: " + e.getMessage();
}
})
.onClose(() -> {
// 清理所有临时文件
tempFiles.forEach(path -> {
try {
Files.deleteIfExists(path);
System.out.println("临时文件已删除: " + path);
} catch (IOException e) {
System.err.println("删除临时文件失败: " + path);
}
});
});
} catch (IOException e) {
// 如果创建失败,清理已创建的文件
tempFiles.forEach(path -> {
try {
Files.deleteIfExists(path);
} catch (IOException ignored) {}
});
throw new RuntimeException(e);
}
}
⚡ 性能和内存管理
// 大文件处理的内存管理
public Stream<String> processLargeFile(Path filePath) {
return Files.lines(filePath, StandardCharsets.UTF_8)
.onClose(() -> {
// 通知垃圾回收
System.gc();
System.out.println("处理大文件完成,建议进行垃圾回收");
});
}
// 批量处理时的资源池管理
private final ExecutorService executor = Executors.newFixedThreadPool(10);
public Stream<Future<String>> parallelProcess(List<Task> tasks) {
return tasks.stream()
.map(task -> executor.submit(() -> processTask(task)))
.onClose(() -> {
// 所有任务完成后关闭线程池
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
System.out.println("线程池已关闭");
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
});
}
⚠️ 注意事项和最佳实践
// ✅ 推荐:使用 try-with-resources
try (Stream<String> lines = Files.lines(path)
.onClose(() -> cleanup())) {
lines.forEach(System.out::println);
}
// 自动调用 close(),确保回调执行
// ✅ 推荐:在 finally 中关闭
Stream<String> stream = Files.lines(path).onClose(() -> cleanup());
try {
stream.forEach(System.out::println);
} finally {
stream.close(); // 确保回调执行
}
// ❌ 避免:忘记关闭Stream
Stream<String> stream = Files.lines(path).onClose(() -> cleanup());
stream.forEach(System.out::println);
// 如果没有显式调用 close(),onClose 回调不会执行
// ✅ 推荐:异常处理
Stream<String> stream = Files.lines(path)
.onClose(() -> {
try {
cleanup();
} catch (Exception e) {
// 记录错误但不抛出异常
System.err.println("清理失败: " + e.getMessage());
}
});
- 必须手动关闭:
onClose()回调只有在调用close()方法时才会执行 - 异常处理:回调中的异常会被静默忽略或记录到控制台
- 执行顺序:多个
onClose()处理器按注册的相反顺序执行 - 主要用于:文件流、数据库连接、网络连接等需要显式关闭的资源
onClose() 提供了优雅的资源管理机制,特别适合处理需要显式关闭的资源,如文件流和数据库连接!
综合实战案例 - 学生成绩分析系统
结合多个Stream操作,实现复杂的业务逻辑处理。
📋 业务场景:学生成绩多维度分析
// 输入数据:List<Student> students = getAllStudents();
// 综合分析需求:
// 1. 筛选活跃学生
// 2. 按专业分组
// 3. 计算每个专业的统计信息
// 4. 找出各专业的优秀学生
// 5. 生成综合报告
🔧 综合解决方案:
public class StudentAnalysisReport {
public void generateComprehensiveReport(List<Student> students) {
// 1. 筛选活跃学生并按专业分组
Map<String, List<Student>> activeByMajor = students.stream()
.filter(Student::isActive) // 筛选操作
.collect(Collectors.groupingBy(Student::getMajor)); // 分组操作
// 2. 计算各专业统计信息
Map<String, MajorStatistics> statisticsMap = activeByMajor.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> calculateMajorStatistics(entry.getValue())
));
// 3. 找出各专业前3名优秀学生
Map<String, List<Student>> topStudentsByMajor = activeByMajor.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().stream()
.sorted(Comparator.comparing(Student::getScore).reversed())
.limit(3)
.collect(Collectors.toList())
));
// 4. 生成排名前10的全校优秀学生
List<Student> top10Overall = students.stream()
.filter(Student::isActive)
.sorted(Comparator
.comparing(Student::getScore).reversed()
.thenComparing(Student::getName))
.limit(10)
.collect(Collectors.toList());
// 5. 按年龄段分析成绩分布
Map<String, Double> averageScoreByAgeGroup = students.stream()
.filter(Student::isActive)
.collect(Collectors.groupingBy(
student -> {
int age = student.getAge();
if (age < 18) return "18岁以下";
else if (age < 22) return "18-21岁";
else if (age < 25) return "22-24岁";
else return "25岁以上";
},
Collectors.averagingDouble(Student::getScore)
));
// 6. 输出综合报告
printComprehensiveReport(statisticsMap, topStudentsByMajor,
top10Overall, averageScoreByAgeGroup);
}
private MajorStatistics calculateMajorStatistics(List<Student> majorStudents) {
IntSummaryStatistics ageStats = majorStudents.stream()
.mapToInt(Student::getAge)
.summaryStatistics();
DoubleSummaryStatistics scoreStats = majorStudents.stream()
.mapToDouble(Student::getScore)
.summaryStatistics();
long highScoreCount = majorStudents.stream()
.mapToLong(s -> s.getScore() >= 90 ? 1 : 0)
.sum();
return new MajorStatistics(
majorStudents.size(),
ageStats.getAverage(),
scoreStats.getAverage(),
scoreStats.getMax(),
highScoreCount
);
}
}
📊 复杂数据转换示例:
// 学生课程成绩转换和统计
public List<StudentCourseSummary> generateCourseSummary(List<Student> students) {
return students.stream()
.filter(Student::isActive) // 筛选活跃学生
.flatMap(student -> student.getCourses().stream() // 扁平化课程列表
.map(course -> new StudentCourseInfo(student, course))) // 组合学生和课程信息
.collect(Collectors.groupingBy( // 按课程分组
StudentCourseInfo::getCourseId,
Collectors.collectingAndThen(
Collectors.toList(),
courseInfos -> {
double averageScore = courseInfos.stream()
.mapToDouble(StudentCourseInfo::getScore)
.average()
.orElse(0.0);
long passCount = courseInfos.stream()
.mapToLong(info -> info.getScore() >= 60 ? 1 : 0)
.sum();
return new StudentCourseSummary(
courseInfos.get(0).getCourseName(),
courseInfos.size(),
averageScore,
(double) passCount / courseInfos.size() * 100
);
}
)
))
.values()
.stream()
.sorted(Comparator.comparing(StudentCourseSummary::getAverageScore).reversed())
.collect(Collectors.toList());
}
⚡ 性能优化实战:
// 大数据量处理优化
public void processLargeDataSet(List<LargeData> largeDataSet) {
// 并行处理 + 短路操作优化
List<LargeData> result = largeDataSet.parallelStream()
.filter(this::quickFilter) // 快速筛选
.filter(this::complexFilter) // 复杂筛选
.map(this::expensiveTransformation) // CPU密集型转换
.distinct() // 去重
.sorted(Comparator.comparing(LargeData::getPriority))
.limit(1000) // 短路操作,避免处理多余数据
.collect(Collectors.toList());
// 批量处理优化
int batchSize = 1000;
List<List<LargeData>> batches = IntStream.range(0, (largeDataSet.size() + batchSize - 1) / batchSize)
.mapToObj(i -> largeDataSet.subList(i * batchSize, Math.min((i + 1) * batchSize, largeDataSet.size())))
.collect(Collectors.toList());
batches.parallelStream()
.forEach(this::processBatch);
}
Stream API的强大之处在于可以灵活组合各种操作,实现复杂的数据处理逻辑!
最佳实践与性能优化
掌握Stream API的使用技巧,让你的代码既优雅又高效!
✅ 使用场景判断
// 适合使用Stream的场景
public void goodStreamUsage() {
// 1. 复杂数据转换
List<StudentDTO> dtos = students.stream()
.filter(Student::isActive)
.map(this::convertToDTO)
.sorted(Comparator.comparing(StudentDTO::getScore))
.collect(Collectors.toList());
// 2. 数据聚合统计
Map<String, Long> countByMajor = students.stream()
.collect(Collectors.groupingBy(Student::getMajor, Collectors.counting()));
// 3. 大数据量处理
List<Result> results = largeDataSet.parallelStream()
.filter(this::heavyFilter)
.map(this::heavyComputation)
.collect(Collectors.toList());
}
// 不适合使用Stream的场景
public void avoidStreamOveruse() {
// 1. 简单遍历 - 传统for更清晰
for (Student student : students) {
System.out.println(student.getName());
}
// 而不是:students.stream().forEach(s -> System.out.println(s.getName()));
// 2. 需要索引的操作
for (int i = 0; i < students.size(); i++) {
Student student = students.get(i);
System.out.println("序号" + i + ": " + student.getName());
}
// 3. 需要修改原集合的操作
List<Student> copy = new ArrayList<>(students);
copy.removeIf(s -> !s.isActive()); // 直接修改,更高效
}
🚀 性能优化技巧
// 1. 使用基本类型Stream避免装箱
IntStream ages = students.stream()
.mapToInt(Student::getAge); // 避免Integer装箱
DoubleStream scores = students.stream()
.mapToDouble(Student::getScore); // 避免Double装箱
// 2. 合理使用短路操作
List<Student> topStudents = students.stream()
.sorted(Comparator.comparing(Student::getScore).reversed())
.limit(10) // 只处理需要的数量,减少排序开销
.collect(Collectors.toList());
// 3. 避免多次收集
// 不推荐:多次collect
List<Student> filtered = students.stream()
.filter(s -> s.getAge() >= 18)
.collect(Collectors.toList());
List<String> names = filtered.stream()
.map(Student::getName)
.collect(Collectors.toList());
// 推荐:一次链式调用
List<String> namesOptimized = students.stream()
.filter(s -> s.getAge() >= 18)
.map(Student::getName)
.collect(Collectors.toList());
// 4. 预编译Comparator
private static final Comparator<Student> SCORE_COMPARATOR =
Comparator.comparing(Student::getScore);
List<Student> sorted = students.stream()
.sorted(SCORE_COMPARATOR)
.collect(Collectors.toList());
🔧 调试技巧
// 使用peek()进行调试
List<Student> result = students.stream()
.filter(s -> s.getAge() >= 18)
.peek(s -> System.out.println("过滤后: " + s.getName())) // 调试点1
.map(Student::getName)
.peek(name -> System.out.println("映射后: " + name)) // 调试点2
.filter(name -> name.length() > 2)
.collect(Collectors.toList());
// 自定义调试收集器
public class DebuggingCollector<T> implements Collector<T, ?, List<T>> {
private final String prefix;
@Override
public Supplier<List<T>> supplier() {
System.out.println(prefix + " - 开始收集");
return ArrayList::new;
}
@Override
public BiConsumer<List<T>, T> accumulator() {
return (list, item) -> {
System.out.println(prefix + " - 添加元素: " + item);
list.add(item);
};
}
// ... 其他方法
}
⚠️ 常见陷阱避免
// 1. 避免修改Stream中的元素
// 错误:尝试修改Stream中的元素
List<Student> result = students.stream()
.peek(student -> student.setScore(student.getScore() + 10)) // 副作用!
.collect(Collectors.toList());
// 正确:在map中创建新对象
List<Student> result = students.stream()
.map(student -> {
Student newStudent = new Student(student);
newStudent.setScore(student.getScore() + 10);
return newStudent;
})
.collect(Collectors.toList());
// 2. 避免重复操作
// 错误:重复计算
students.stream().mapToInt(Student::getScore).sum();
students.stream().mapToInt(Student::getScore).average();
// 正确:一次性计算
IntSummaryStatistics stats = students.stream()
.mapToInt(Student::getScore)
.summaryStatistics();
double sum = stats.getSum();
double average = stats.getAverage();
// 3. 注意并行Stream的线程安全
// 错误:非线程安全的操作
List<StringBuilder> result = students.parallelStream()
.collect(ArrayList::new,
(list, student) -> list.add(new StringBuilder(student.getName())),
List::addAll);
// 正确:使用线程安全的方式
List<String> result = students.parallelStream()
.map(Student::getName)
.collect(Collectors.toList());
Stream API 是强大的工具,但要理性使用!
简单操作用传统方式,复杂处理用Stream优雅解决,性能优化要具体分析!