Stream 流是 JDK 8 引入的一套全新 API(java.util.stream.),可以用来操作集合或者数组的数据。它结合函数式编程风格(Lambda 表达式),让数据处理变得更加优雅和高效。
💡 什么是 Stream 流 #
Stream 流是 Java 8 中处理集合数据的一种声明式方式。它不是数据结构,不存储数据,而是一个数据处理管道,可以对数据源(集合、数组等)进行复杂的查找、过滤、映射、聚合等操作。
核心特点 #
- 不存储数据:Stream 不会存储元素,它只是传递数据源的视图
- 函数式编程:操作通过 Lambda 表达式实现,代码简洁
- 惰性执行:中间操作不会立即执行,只有终止操作触发时才会计算
- 可消费性:Stream 只能使用一次,操作后不可重复使用
- 支持并行:可轻松切换为并行流,充分利用多核 CPU
🎯 Stream 的核心优势 #
| 优势 | 传统方式 | Stream 方式 |
|---|---|---|
| 代码简洁性 | 需要编写大量循环、判断语句 | 链式调用,一行代码完成复杂操作 |
| 可读性 | 逻辑分散在多个代码块中 | 声明式编程,业务意图清晰明了 |
| 性能优化 | 手动优化困难 | 内部迭代优化 + 惰性求值 + 短路操作 |
| 并行处理 | 需要手动管理线程 | 一个方法调用即可切换并行模式 |
| 函数式组合 | 难以复用和组合操作 | 可灵活组合各种操作符 |
🔧 Stream 的主要功能 #
1️⃣ 创建 Stream #
| 创建方式 | 说明 |
|---|---|
[collection.stream](http://collection.stream)() |
从集合创建串行流 |
collection.parallelStream() |
从集合创建并行流 |
[Arrays.stream](http://Arrays.stream)(array) |
从数组创建流 |
Stream.of(T... values) |
从指定值创建流 |
Stream.generate(Supplier) |
生成无限流(需要 limit 限制) |
Stream.iterate(seed, UnaryOperator) |
迭代生成流 |
Files.lines(Path) |
从文件读取行创建流 |
2️⃣ 中间操作(Intermediate Operations) #
这些操作返回新的 Stream,支持链式调用,惰性执行:
| 操作 | 作用 | 示例 |
|---|---|---|
filter(Predicate) |
过滤符合条件的元素 | stream.filter(x -> x > 10) |
map(Function) |
将元素映射为其他形式 | [stream.map](http://stream.map)(String::toUpperCase) |
flatMap(Function) |
将每个元素映射为流,然后扁平化 | stream.flatMap(List::stream) |
distinct() |
去除重复元素 | stream.distinct() |
sorted() |
自然排序 | stream.sorted() |
sorted(Comparator) |
自定义排序 | stream.sorted(Comparator.reverseOrder()) |
peek(Consumer) |
对每个元素执行操作(调试用) | stream.peek(System.out::println) |
limit(long) |
限制流的大小 | stream.limit(5) |
skip(long) |
跳过前 n 个元素 | stream.skip(3) |
3️⃣ 终止操作(Terminal Operations) #
触发流的计算,返回非 Stream 结果:
| 操作 | 作用 | 示例 |
|---|---|---|
forEach(Consumer) |
遍历每个元素 | stream.forEach(System.out::println) |
collect(Collector) |
收集结果到集合 | stream.collect(Collectors.toList()) |
toArray() |
转换为数组 | stream.toArray(String[]::new) |
reduce(BinaryOperator) |
将流中元素反复结合,得到一个值 | stream.reduce((a, b) -> a + b) |
count() |
返回流中元素个数 | stream.count() |
max(Comparator) |
返回最大值 | stream.max(Comparator.naturalOrder()) |
min(Comparator) |
返回最小值 | stream.min(Comparator.naturalOrder()) |
anyMatch(Predicate) |
是否有元素匹配 | stream.anyMatch(x -> x > 10) |
allMatch(Predicate) |
是否所有元素匹配 | stream.allMatch(x -> x > 0) |
noneMatch(Predicate) |
是否没有元素匹配 | stream.noneMatch(x -> x < 0) |
findFirst() |
返回第一个元素 | stream.findFirst() |
findAny() |
返回任意一个元素 | stream.findAny() |
📝 Stream 的使用流程 #
创建流 → 中间操作(可多次链式调用) → 终止操作
数据源 → stream() → filter() → map() → sorted() → collect()
↓ ↓ ↓ ↓ ↓ ↓
集合 创建流 过滤数据 转换数据 排序数据 收集结果
💻 代码开发实例 #
🔹 实例 1:基础过滤与映射 #
需求:从员工列表中筛选出工资大于 8000 的员工姓名
import java.util.*;
import [java.util.stream](http://java.util.stream).*;
public class StreamExample1 {
public static void main(String[] args) {
// 创建员工列表
List<Employee> employees = Arrays.asList(
new Employee("张三", 7500),
new Employee("李四", 9200),
new Employee("王五", 8800),
new Employee("赵六", 6500),
new Employee("钱七", 10000)
);
// 使用 Stream 筛选并获取姓名
List<String> highSalaryNames = [employees.stream](http://employees.stream)()
.filter(emp -> emp.getSalary() > 8000) // 过滤
.map(Employee::getName) // 映射
.collect(Collectors.toList()); // 收集
System.out.println("高薪员工:" + highSalaryNames);
// 输出:高薪员工:[李四, 王五, 钱七]
}
}
class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
[this.name](http://this.name) = name;
this.salary = salary;
}
public String getName() { return name; }
public double getSalary() { return salary; }
}
🔹 实例 2:排序与限制 #
需求:获取成绩前 3 名的学生
import java.util.*;
import [java.util.stream](http://java.util.stream).*;
public class StreamExample2 {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("小明", 85),
new Student("小红", 92),
new Student("小刚", 78),
new Student("小丽", 95),
new Student("小华", 88)
);
// 按成绩降序排序,取前 3 名
List<Student> top3 = [students.stream](http://students.stream)()
.sorted(Comparator.comparing(Student::getScore).reversed())
.limit(3)
.collect(Collectors.toList());
System.out.println("前三名:");
top3.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));
/* 输出:
前三名:
小丽: 95
小红: 92
小华: 88
*/
}
}
class Student {
private String name;
private int score;
public Student(String name, int score) {
[this.name](http://this.name) = name;
this.score = score;
}
public String getName() { return name; }
public int getScore() { return score; }
}
🔹 实例 3:分组与统计 #
需求:按部门分组统计员工人数和平均工资
import java.util.*;
import [java.util.stream](http://java.util.stream).*;
public class StreamExample3 {
public static void main(String[] args) {
List<Employee2> employees = Arrays.asList(
new Employee2("张三", "技术部", 8000),
new Employee2("李四", "技术部", 9500),
new Employee2("王五", "销售部", 7000),
new Employee2("赵六", "销售部", 7500),
new Employee2("钱七", "技术部", 11000)
);
// 按部门分组
Map<String, List<Employee2>> byDept = [employees.stream](http://employees.stream)()
.collect(Collectors.groupingBy(Employee2::getDepartment));
System.out.println("=== 按部门分组 ===");
byDept.forEach((dept, empList) -> {
System.out.println(dept + ": " + empList.size() + "人");
});
// 计算各部门平均工资
Map<String, Double> avgSalaryByDept = [employees.stream](http://employees.stream)()
.collect(Collectors.groupingBy(
Employee2::getDepartment,
Collectors.averagingDouble(Employee2::getSalary)
));
System.out.println("\n=== 各部门平均工资 ===");
avgSalaryByDept.forEach((dept, avg) -> {
System.out.printf("%s: %.2f元\n", dept, avg);
});
/* 输出:
=== 按部门分组 ===
技术部: 3人
销售部: 2人
=== 各部门平均工资 ===
技术部: 9500.00元
销售部: 7250.00元
*/
}
}
class Employee2 {
private String name;
private String department;
private double salary;
public Employee2(String name, String department, double salary) {
[this.name](http://this.name) = name;
this.department = department;
this.salary = salary;
}
public String getName() { return name; }
public String getDepartment() { return department; }
public double getSalary() { return salary; }
}
🔹 实例 4:flatMap 扁平化处理 #
需求:将多个班级的学生合并为一个列表
import java.util.*;
import [java.util.stream](http://java.util.stream).*;
public class StreamExample4 {
public static void main(String[] args) {
List<String> class1 = Arrays.asList("张三", "李四", "王五");
List<String> class2 = Arrays.asList("赵六", "钱七");
List<String> class3 = Arrays.asList("孙八", "周九", "吴十");
List<List<String>> school = Arrays.asList(class1, class2, class3);
// 使用 flatMap 扁平化
List<String> allStudents = [school.stream](http://school.stream)()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println("所有学生:" + allStudents);
// 输出:所有学生:[张三, 李四, 王五, 赵六, 钱七, 孙八, 周九, 吴十]
System.out.println("学生总数:" + allStudents.size());
// 输出:学生总数:8
}
}
🔹 实例 5:去重与统计 #
需求:统计订单中不同商品的数量
import java.util.*;
import [java.util.stream](http://java.util.stream).*;
public class StreamExample5 {
public static void main(String[] args) {
List<String> orders = Arrays.asList(
"手机", "电脑", "手机", "平板",
"电脑", "手机", "耳机", "平板"
);
// 去重并统计
long distinctCount = [orders.stream](http://orders.stream)()
.distinct()
.count();
System.out.println("不同商品种类:" + distinctCount);
// 输出:不同商品种类:4
// 统计每种商品的数量
Map<String, Long> productCount = [orders.stream](http://orders.stream)()
.collect(Collectors.groupingBy(
product -> product,
Collectors.counting()
));
System.out.println("\n各商品订单数:");
productCount.forEach((product, count) -> {
System.out.println(product + ": " + count + "单");
});
/* 输出:
各商品订单数:
平板: 2单
电脑: 2单
耳机: 1单
手机: 3单
*/
}
}
🔹 实例 6:reduce 聚合操作 #
需求:计算购物车总价
import java.util.*;
import [java.util.stream](http://java.util.stream).*;
public class StreamExample6 {
public static void main(String[] args) {
List<Product> cart = Arrays.asList(
new Product("手机", 2999.0),
new Product("耳机", 299.0),
new Product("充电器", 99.0),
new Product("数据线", 39.0)
);
// 计算总价
double totalPrice = [cart.stream](http://cart.stream)()
.map(Product::getPrice)
.reduce(0.0, Double::sum);
System.out.printf("购物车总价:%.2f元\n", totalPrice);
// 输出:购物车总价:3436.00元
// 使用 sum() 方法(更简洁)
double total2 = [cart.stream](http://cart.stream)()
.mapToDouble(Product::getPrice)
.sum();
System.out.printf("购物车总价:%.2f元\n", total2);
}
}
class Product {
private String name;
private double price;
public Product(String name, double price) {
[this.name](http://this.name) = name;
this.price = price;
}
public String getName() { return name; }
public double getPrice() { return price; }
}
🔹 实例 7:并行流提升性能 #
需求:计算大量数据的总和
import java.util.*;
import [java.util.stream](http://java.util.stream).*;
public class StreamExample7 {
public static void main(String[] args) {
// 生成 1000 万个随机数
List<Integer> numbers = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 10_000_000; i++) {
numbers.add(random.nextInt(100));
}
// 串行流计算
long start1 = System.currentTimeMillis();
long sum1 = [numbers.stream](http://numbers.stream)()
.mapToLong(Integer::longValue)
.sum();
long time1 = System.currentTimeMillis() - start1;
// 并行流计算
long start2 = System.currentTimeMillis();
long sum2 = numbers.parallelStream()
.mapToLong(Integer::longValue)
.sum();
long time2 = System.currentTimeMillis() - start2;
System.out.println("串行流结果:" + sum1 + ",耗时:" + time1 + "ms");
System.out.println("并行流结果:" + sum2 + ",耗时:" + time2 + "ms");
System.out.println("性能提升:" + (time1 - time2) + "ms");
/* 输出示例:
串行流结果:495059712,耗时:156ms
并行流结果:495059712,耗时:52ms
性能提升:104ms
*/
}
}
⚡ 性能优化建议 #
何时使用并行流?
✅ 适合使用并行流的场景:
- 数据量大(通常 > 10000)
- 操作计算密集(复杂的 map、filter 等)
- 无状态操作(操作之间相互独立)
❌ 不适合使用并行流的场景:
- 数据量小(并行开销 > 性能收益)
- 操作依赖顺序(如有状态的 sorted、distinct)
- 涉及 I/O 操作(如数据库、文件读写)
- 使用 ArrayList 等易分割的数据结构效果更好,LinkedList 等链表结构不适合
📚 常用 Collectors 收集器 #
| 收集器 | 说明 |
|---|---|
Collectors.toList() |
收集为 List |
Collectors.toSet() |
收集为 Set(自动去重) |
Collectors.toMap() |
收集为 Map |
Collectors.joining() |
连接字符串 |
Collectors.groupingBy() |
分组 |
Collectors.partitioningBy() |
分区(true/false 两组) |
Collectors.counting() |
计数 |
Collectors.summingInt() |
求和 |
Collectors.averagingDouble() |
求平均值 |
Collectors.maxBy() |
求最大值 |
Collectors.minBy() |
求最小值 |
🎓 学习建议与最佳实践 #
✨ Stream 使用最佳实践
- 优先使用 Stream:对于集合操作,Stream 通常比传统循环更清晰
- 避免过长的链式调用:超过 5 个操作考虑拆分或添加注释
- 注意惰性求值:中间操作不会立即执行,调试时使用
peek()查看中间结果 - 合理使用并行流:不是所有场景都适合并行,小数据集反而会降低性能
- 避免副作用:Lambda 表达式中不要修改外部变量
- Stream 只能使用一次:需要多次遍历时重新创建 Stream
- 使用方法引用:如
String::toUpperCase比 Lambda 更简洁 - 选择合适的终止操作:
forEach用于遍历,collect用于收集结果
📌 总结 #
Stream API 是 Java 8 引入的革命性特性,它将函数式编程的思想带入 Java,让数据处理变得更加优雅、简洁、高效。
核心要点:
- 🎯 声明式编程:关注"做什么"而非"怎么做"
- ⚡ 性能优化:惰性求值 + 短路操作 + 并行流
- 🔗 链式操作:流畅的 API 设计,可读性强
- 🛠️ 丰富的操作符:过滤、映射、排序、分组、聚合一应俱全
掌握 Stream API 是现代 Java 开发的必备技能,它能大幅提升代码质量和开发效率!