掌握Java 8引入的Stream API,提升集合数据处理效率和代码可读性
Stream API简介
Java 8引入的Stream API是处理集合数据的革命性特性,它允许开发者以声明式方式处理数据集合。与传统的迭代方式相比,Stream API提供了更高效、更易读的数据处理方式,同时充分利用多核架构的优势。
Stream不是数据结构,而是从数据源(如集合、数组或I/O资源)获取的元素序列,支持聚合操作。Stream操作分为中间操作(返回Stream)和终止操作(返回结果或副作用)。
核心概念
理解Stream API需要掌握以下几个核心概念:
- 流(Stream):元素序列,支持顺序和并行聚合操作
- 数据源:流的来源,如集合、数组或生成器函数
- 中间操作:返回新流的操作,如filter、map、sorted
- 终止操作:产生结果或副作用的操作,如collect、forEach、reduce
- 流水线:一系列流操作链
- 内部迭代:流操作在内部处理迭代,无需显式循环
创建Stream
有多种方式可以创建Stream:
从集合创建
List list = Arrays.asList("a", "b", "c");
Stream stream = list.stream(); // 顺序流
Stream parallelStream = list.parallelStream(); // 并行流
从数组创建
String[] array = {"a", "b", "c"};
Stream stream = Arrays.stream(array);
使用Stream.of()
Stream stream = Stream.of("a", "b", "c");
使用Stream.generate()
Stream stream = Stream.generate(() -> "element").limit(10);
使用Stream.iterate()
Stream stream = Stream.iterate(0, n -> n + 2).limit(10);
中间操作
中间操作是构建处理流水线的关键,它们返回新的Stream,允许链式调用:
filter() – 过滤元素
List list = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List filtered = list.stream()
.filter(string -> !string.isEmpty())
.collect(Collectors.toList()); // 过滤空字符串
map() – 元素转换
List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List squares = numbers.stream()
.map(i -> i * i)
.collect(Collectors.toList()); // 计算平方
flatMap() – 扁平化流
List<List> listOfLists = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d"),
Arrays.asList("e", "f")
);
List flatList = listOfLists.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList()); // 将多个列表合并为一个
distinct() – 去重
List numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.distinct() // 去重
.forEach(System.out::println);
sorted() – 排序
List names = Arrays.asList("Reflection", "Collection", "Stream");
List sorted = names.stream()
.sorted() // 自然排序
.collect(Collectors.toList());
终止操作
终止操作会触发流的处理并返回结果:
collect() – 将流转换为集合
List list = Arrays.asList("a", "b", "c");
List result = list.stream()
.collect(Collectors.toList()); // 转换为List
// 分组操作
Map<Integer, List> groupByLength = list.stream()
.collect(Collectors.groupingBy(String::length));
forEach() – 迭代每个元素
List numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.forEach(System.out::println); // 打印每个元素
reduce() – 将流缩减为单个值
List numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, Integer::sum); // 求和
count() – 统计元素数量
long count = Arrays.asList("a", "b", "c").stream().count(); // 3
anyMatch()/allMatch()/noneMatch() – 匹配检查
List numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean hasEven = numbers.stream()
.anyMatch(n -> n % 2 == 0); // 检查是否有偶数
并行流处理
Java Stream API支持并行处理,可以充分利用多核处理器的优势:
List list = Arrays.asList("a", "b", "c", "d", "e");
// 顺序处理
long count = list.stream().filter(s -> s.startsWith("a")).count();
// 并行处理
long parallelCount = list.parallelStream().filter(s -> s.startsWith("a")).count();
使用并行流时需要注意:
- 数据量越大,并行处理优势越明显
- 避免在并行流中使用有状态的操作
- 注意线程安全问题
- 某些操作(如findFirst)在并行流中性能可能不如顺序流
实战案例:员工数据处理
下面通过一个完整的示例展示如何使用Stream API处理员工数据:
import java.util.*;
import java.util.stream.*;
class Employee {
private String name;
private int age;
private String department;
private double salary;
public Employee(String name, int age, String department, double salary) {
this.name = name;
this.age = age;
this.department = department;
this.salary = salary;
}
// Getter方法省略...
@Override
public String toString() {
return "Employee{name='" + name + "', age=" + age +
", department='" + department + "', salary=" + salary + "}";
}
}
public class StreamExample {
public static void main(String[] args) {
List employees = Arrays.asList(
new Employee("张三", 25, "技术部", 8000),
new Employee("李四", 30, "技术部", 12000),
new Employee("王五", 28, "市场部", 9000),
new Employee("赵六", 35, "市场部", 11000),
new Employee("钱七", 40, "人事部", 10000)
);
// 1. 过滤年龄大于30的员工
System.out.println("年龄大于30的员工:");
employees.stream()
.filter(e -> e.getAge() > 30)
.forEach(System.out::println);
// 2. 按部门分组
System.out.println("n按部门分组:");
Map<String, List> byDepartment = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
byDepartment.forEach((dept, emps) -> {
System.out.println(dept + ": " + emps);
});
// 3. 计算每个部门的平均工资
System.out.println("n各部门平均工资:");
Map avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(Employee::getSalary)
));
avgSalaryByDept.forEach((dept, avg) -> {
System.out.println(dept + ": " + String.format("%.2f", avg));
});
// 4. 找出工资最高的员工
System.out.println("n工资最高的员工:");
employees.stream()
.max(Comparator.comparingDouble(Employee::getSalary))
.ifPresent(System.out::println);
// 5. 统计技术部员工数量
long techCount = employees.stream()
.filter(e -> "技术部".equals(e.getDepartment()))
.count();
System.out.println("n技术部员工数量: " + techCount);
}
}
最佳实践与性能考量
使用Stream API时,遵循以下最佳实践可以提高代码质量和性能:
1. 优先使用方法引用
// 而不是: .map(s -> s.length())
.map(String::length)
2. 避免在流中修改外部状态
// 错误做法 - 修改外部变量
List result = new ArrayList();
stream.forEach(item -> result.add(item));
// 正确做法 - 使用collect
List result = stream.collect(Collectors.toList());
3. 谨慎使用并行流
并行流并不总是更快,对于小数据集或某些操作,顺序流可能更高效。
4. 使用原始类型特化流
// 对于原始类型,使用特化流提高性能
IntStream.range(0, 100).sum(); // 优于Stream
5. 注意流的关闭
基于IO的流(如Files.lines())需要显式关闭,可以使用try-with-resources:
try (Stream lines = Files.lines(Paths.get("file.txt"))) {
lines.forEach(System.out::println);
}