Stream 简介
Stream 是 JDK 8 中处理集合(Collection)数据的新抽象,它可以让你以声明式的方式处理数据,类似于使用 SQL 语句进行数据库查询。
List names = Arrays.asList("Alice", "Bob", "Charlie", "David");
java
// 传统方式
List<String> result = new ArrayList<>();
for (String name : names) {
if (name.startsWith("A")) {
result.add(name.toUpperCase());
}
}
// Stream 方式
List<String> streamResult = names.stream()
.filter(name -> name.startsWith("A"))
.map(String::toUpperCase)
.collect(Collectors.toList());
Stream 的特点
- 不是数据结构:Stream 不会存储数据,只是对数据源进行计算
- 不修改源数据:对 Stream 的操作不会影响原始数据源
- 惰性执行:中间操作是惰性的,只有在终止操作时才会执行
- 可消费性:Stream 只能被消费一次
Stream 操作分类
| 操作类型 | 方法示例 | 返回值 | 特点 |
|---|---|---|---|
| 中间操作 | filter(), map(), sorted() | Stream | 惰性执行,可链式调用 |
| 终止操作 | forEach(), collect(), count() | 非Stream | 立即执行,关闭流 |
创建 Stream
1. 从集合创建
java
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
2. 从数组创建
java
String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
3. 使用 Stream.of()
java
Stream<String> stream = Stream.of("a", "b", "c");
4. 创建数值流
java
IntStream intStream = IntStream.range(1, 5); // 1,2,3,4
IntStream closedStream = IntStream.rangeClosed(1, 5); // 1,2,3,4,5
5. 创建无限流
java
// 生成10个随机数
Stream<Double> randomStream = Stream.generate(Math::random).limit(10);
// 创建斐波那契数列
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]})
.limit(10)
.map(t -> t[0])
.forEach(System.out::println);
中间操作
1. filter() - 过滤
java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> result = names.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toList());
// 结果: [Alice, Charlie, David]
2. map() - 映射
java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
// 结果: [5, 3, 7]
3. flatMap() - 扁平化映射
java
List<List<String>> listOfLists = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d")
);
List<String> flatList = listOfLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
// 结果: [a, b, c, d]
4. distinct() - 去重
java
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
// 结果: [1, 2, 3]
5. sorted() - 排序
java
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
List<String> sortedNames = names.stream()
.sorted()
.collect(Collectors.toList());
// 结果: [Alice, Bob, Charlie]
// 自定义排序
List<String> customSorted = names.stream()
.sorted((a, b) -> b.length() - a.length())
.collect(Collectors.toList());
// 结果: [Charlie, Alice, Bob]
6. limit() 和 skip() - 限制和跳过
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> limited = numbers.stream()
.limit(5)
.collect(Collectors.toList());
// 结果: [1, 2, 3, 4, 5]
List<Integer> skipped = numbers.stream()
.skip(5)
.collect(Collectors.toList());
// 结果: [6, 7, 8, 9, 10]
7. peek() - 查看元素(主要用于调试)
java
List<String> result = Stream.of("one", "two", "three")
.filter(s -> s.length() > 3)
.peek(s -> System.out.println("Filtered value: " + s))
.map(String::toUpperCase)
.peek(s -> System.out.println("Mapped value: " + s))
.collect(Collectors.toList());
终止操作
1. forEach() - 遍历
java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().forEach(System.out::println);
2. collect() - 收集
java
// 转换为List
List<String> list = stream.collect(Collectors.toList());
// 转换为Set
Set<String> set = stream.collect(Collectors.toSet());
// 转换为Map
Map<String, Integer> map = stream.collect(
Collectors.toMap(s -> s, String::length)
);
// 连接字符串
String joined = stream.collect(Collectors.joining(", "));
// 分组
Map<Integer, List<String>> grouped = names.stream()
.collect(Collectors.groupingBy(String::length));
// 分区
Map<Boolean, List<String>> partitioned = names.stream()
.collect(Collectors.partitioningBy(s -> s.length() > 3));
3. reduce() - 归约
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 求和
Optional<Integer> sum = numbers.stream().reduce(Integer::sum);
// 或
Integer sum2 = numbers.stream().reduce(0, Integer::sum);
// 求最大值
Optional<Integer> max = numbers.stream().reduce(Integer::max);
// 字符串连接
Optional<String> concat = Stream.of("a", "b", "c").reduce(String::concat);
4. 匹配操作
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); // false
boolean anyEven = numbers.stream().anyMatch(n -> n % 2 == 0); // true
boolean noneEven = numbers.stream().noneMatch(n -> n % 2 == 0); // false
5. 查找操作
java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> first = names.stream().findFirst();
Optional<String> any = names.stream().findAny();
6. 统计操作
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
long count = numbers.stream().count();
IntSummaryStatistics stats = numbers.stream()
.mapToInt(Integer::intValue)
.summaryStatistics();
System.out.println("最大值: " + stats.getMax());
System.out.println("最小值: " + stats.getMin());
System.out.println("总和: " + stats.getSum());
System.out.println("平均值: " + stats.getAverage());
并行流
创建并行流
java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 方法1:从集合创建
Stream<String> parallelStream = names.parallelStream();
// 方法2:将顺序流转为并行流
Stream<String> parallel = names.stream().parallel();
并行流示例
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
long startTime = System.currentTimeMillis();
long sum = numbers.parallelStream()
.mapToLong(Integer::longValue)
.sum();
long endTime = System.currentTimeMillis();
System.out.println("总和: " + sum);
System.out.println("执行时间: " + (endTime - startTime) + "ms");
并行流注意事项
- 线程安全:确保操作是线程安全的
- 状态无关:避免有状态的lambda表达式
- 顺序敏感:某些操作(如findFirst)在并行流中性能可能更差
- 数据量:小数据量使用并行流可能适得其反
实战示例
示例1:员工数据处理
java
class Employee {
private String name;
private String department;
private double salary;
private int age;
// 构造方法、getter、setter省略
}
List<Employee> employees = Arrays.asList(
new Employee("Alice", "IT", 5000, 25),
new Employee("Bob", "HR", 4000, 30),
new Employee("Charlie", "IT", 6000, 35),
new Employee("David", "Finance", 5500, 28)
);
// 1. 按部门分组,计算每个部门的平均工资
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(Employee::getSalary)
));
// 2. 找出IT部门工资最高的员工
Optional<Employee> highestPaidInIT = employees.stream()
.filter(e -> "IT".equals(e.getDepartment()))
.max(Comparator.comparingDouble(Employee::getSalary));
// 3. 按年龄排序,获取前3名员工
List<Employee> top3ByAge = employees.stream()
.sorted(Comparator.comparingInt(Employee::getAge).reversed())
.limit(3)
.collect(Collectors.toList());
// 4. 统计每个部门的员工数量
Map<String, Long> employeeCountByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.counting()
));
示例2:文件处理
java
// 读取文件并统计单词频率
try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) {
Map<String, Long> wordCount = lines
.flatMap(line -> Arrays.stream(line.split("\s+")))
.map(word -> word.replaceAll("[^a-zA-Z]", "").toLowerCase())
.filter(word -> !word.isEmpty())
.collect(Collectors.groupingBy(
word -> word,
Collectors.counting()
));
// 按频率排序
wordCount.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.forEach(entry ->
System.out.println(entry.getKey() + ": " + entry.getValue())
);
} catch (IOException e) {
e.printStackTrace();
}
示例3:复杂数据转换
java
// 多层数据转换和聚合
class Order {
private String customer;
private List<OrderItem> items;
private LocalDate orderDate;
// 构造方法、getter、setter省略
}
class OrderItem {
private String product;
private int quantity;
private double price;
// 构造方法、getter、setter省略
}
List<Order> orders = // 初始化订单数据
// 计算每个客户的总消费金额
Map<String, Double> customerTotal = orders.stream()
.collect(Collectors.groupingBy(
Order::getCustomer,
Collectors.summingDouble(order ->
order.getItems().stream()
.mapToDouble(item -> item.getQuantity() * item.getPrice())
.sum()
)
));
// 找出最畅销的产品
Optional<String> bestSellingProduct = orders.stream()
.flatMap(order -> order.getItems().stream())
.collect(Collectors.groupingBy(
OrderItem::getProduct,
Collectors.summingInt(OrderItem::getQuantity)
))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey);
最佳实践
- 优先使用方法引用:使代码更简洁
- 避免副作用:保持函数的纯粹性
- 合理使用并行流:根据数据量和操作复杂度决定
- 及时关闭资源:使用try-with-resources处理IO相关的Stream
- 合理使用Optional:避免空指针异常
总结
JDK 8 的 Stream API 为集合操作提供了强大的函数式编程能力,通过链式调用和惰性求值等特性,可以写出更简洁、易读、高效的代码。掌握 Stream API 对于现代 Java 开发至关重要。