Java 8引入的Stream API彻底改变了我们处理集合数据的方式。这种声明式的编程风格让我们能够以更简洁、更易读的方式表达复杂的数据处理逻辑。与传统的for循环相比,Stream操作具有更好的可组合性和并行化潜力。
在实际项目中,我发现很多开发者虽然知道Stream的基本用法,但在面对复杂业务场景时往往束手无策。这主要是因为缺乏系统的练习和对Stream操作背后原理的深入理解。下面我将通过一系列精心设计的练习题,带你逐步掌握Stream的高级用法。
重要提示:Stream操作不会修改源数据,每次操作都会生成新的Stream。这种特性使得Stream非常适合函数式编程范式。
我们先从最基础的filter和map操作开始。假设我们有一个员工列表:
java复制List<Employee> employees = Arrays.asList(
new Employee("张三", 25, 8000),
new Employee("李四", 30, 12000),
new Employee("王五", 28, 10000)
);
练习题1:找出所有年龄大于25岁的员工姓名
java复制List<String> names = employees.stream()
.filter(e -> e.getAge() > 25)
.map(Employee::getName)
.collect(Collectors.toList());
这里有几个关键点需要注意:
练习题2:计算所有员工工资的总和
java复制double totalSalary = employees.stream()
.mapToDouble(Employee::getSalary)
.sum();
使用mapToDouble而不是map,可以避免自动装箱带来的性能损耗。对于数值计算,Java提供了专门的原始类型流(IntStream, LongStream, DoubleStream)。
分组(Grouping)是Stream中最强大的功能之一。我们来看一个复杂点的例子:
练习题3:按年龄段分组统计员工信息
java复制Map<String, List<Employee>> groupedByAge = employees.stream()
.collect(Collectors.groupingBy(e -> {
if(e.getAge() < 25) return "青年";
else if(e.getAge() < 35) return "中年";
else return "资深";
}));
练习题4:获取工资最高的3名员工
java复制List<Employee> top3 = employees.stream()
.sorted(Comparator.comparingDouble(Employee::getSalary).reversed())
.limit(3)
.collect(Collectors.toList());
这里使用了Comparator的comparing方法创建比较器,reversed()实现降序排列,limit限制结果数量。
当处理嵌套集合时,flatMap就派上用场了。考虑以下数据结构:
java复制List<List<Integer>> numberLists = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6)
);
练习题5:将所有数字合并并计算平方和
java复制int sum = numberLists.stream()
.flatMap(List::stream)
.mapToInt(n -> n * n)
.sum();
flatMap将多个流合并为一个流,这在处理树形结构或一对多关系时特别有用。
虽然Java提供了丰富的内置收集器,但有时我们需要自定义收集逻辑。比如:
练习题6:实现一个收集器,将员工按工资范围分组
java复制Collector<Employee, ?, Map<String, List<Employee>>> salaryRangeCollector =
Collector.of(
HashMap::new,
(map, employee) -> {
String range = employee.getSalary() < 10000 ? "普通" : "高薪";
map.computeIfAbsent(range, k -> new ArrayList<>()).add(employee);
},
(map1, map2) -> {
map2.forEach((k, v) -> map1.merge(k, v, (l1, l2) -> {
l1.addAll(l2);
return l1;
}));
return map1;
}
);
某些流操作如findFirst、anyMatch等不需要处理全部元素,合理使用可以提高性能:
java复制boolean hasHighSalary = employees.stream()
.anyMatch(e -> e.getSalary() > 15000);
并行流看似美好,但使用不当反而会降低性能:
java复制// 适合并行化的场景
List<String> names = employees.parallelStream()
.filter(e -> e.getAge() > 25)
.map(Employee::getName)
.collect(Collectors.toList());
并行流适用于:
为了巩固所学,我设计了一个综合练习项目:
项目需求:处理一个订单列表,每个订单包含多个商品项,需要:
java复制// 数据结构示例
class Order {
String customerId;
List<OrderItem> items;
Date orderDate;
}
class OrderItem {
String productId;
String category;
double price;
int quantity;
}
解决方案将涉及流的各种操作组合,建议读者先自行尝试实现,这对掌握Stream API非常有帮助。