Java 流处理:Stream API 与 Lambda 表达式
Java 8 引入了 Stream API 和 Lambda 表达式,彻底改变了处理集合数据的方式。传统 for 循环遍历、筛选、转换数据的代码往往冗长且难以维护,而 Stream 提供了一种声明式、链式调用的风格,让逻辑更清晰、代码更简洁。
准备工作:理解核心概念
Lambda 表达式是一种匿名函数,用于替代匿名内部类,尤其适用于函数式接口(只有一个抽象方法的接口)。它的基本语法是 (参数) -> 表达式 或 (参数) -> { 语句块 }。
Stream(流)不是数据结构,而是对数据源(如 List、Set、数组)的一系列操作管道。Stream 操作分为两类:
- 中间操作(如
filter、map):返回新的 Stream,支持链式调用。 - 终端操作(如
collect、forEach):触发实际计算并结束流。
Stream 具有惰性求值特性——中间操作不会立即执行,只有遇到终端操作时才会从头到尾一次性处理。
基础操作:过滤与遍历
假设有一个 Person 类:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// getter 方法省略
public String getName() { return name; }
public int getAge() { return age; }
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
创建一个人员列表:
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 22)
);
1. 筛选 年龄大于等于 25 的人
people.stream()
.filter(p -> p.getAge() >= 25)
.forEach(p -> System.out.println(p.getName()));
filter接收一个Predicate(返回 boolean 的函数),保留满足条件的元素。- Lambda 表达式
p -> p.getAge() >= 25替代了传统的匿名类写法。
2. 遍历 所有元素并打印
people.stream().forEach(System.out::println);
System.out::println是方法引用,等价于p -> System.out.println(p),进一步简化代码。
数据转换:映射与收集
3. 提取 所有人的姓名,生成字符串列表
List<String> names = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
map接收一个Function,将每个元素转换为另一种类型。Person::getName是方法引用,表示调用getName()方法。collect(Collectors.toList())是终端操作,将流结果收集为List。
4. 转换 年龄为字符串,并按年龄升序排序
List<String> sortedAges = people.stream()
.map(p -> String.valueOf(p.getAge()))
.sorted()
.collect(Collectors.toList());
sorted()默认按自然顺序排序(对字符串是字典序,对数字是数值序)。- 如果想按
Person的年龄排序后再转字符串,应先排序再映射:
List<String> sortedByAge = people.stream()
.sorted((p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()))
.map(p -> String.valueOf(p.getAge()))
.collect(Collectors.toList());
- 这里的
(p1, p2) -> Integer.compare(...)是Comparator的 Lambda 表达式。
聚合操作:统计与归约
5. 计算 平均年龄
OptionalDouble averageAge = people.stream()
.mapToInt(Person::getAge)
.average();
if (averageAge.isPresent()) {
System.out.println("平均年龄: " + averageAge.getAsDouble());
}
mapToInt将对象流转换为IntStream,避免装箱开销。average()返回OptionalDouble,因为列表可能为空。
6. 统计 人数、最大年龄、最小年龄
IntSummaryStatistics stats = people.stream()
.mapToInt(Person::getAge)
.summaryStatistics();
System.out.println("人数: " + stats.getCount());
System.out.println("最大年龄: " + stats.getMax());
System.out.println("最小年龄: " + stats.getMin());
summaryStatistics()一次性获取多个统计值,高效且简洁。
高级用法:分组与多级操作
7. 按年龄是否成年 分组
Map<Boolean, List<Person>> byAdult = people.stream()
.collect(Collectors.partitioningBy(p -> p.getAge() >= 18));
partitioningBy将集合分为两组:true(成年)和false(未成年)。- 结果是一个包含两个键的
Map。
8. 按年龄段分组(例如:青年 <30,中年 ≥30)
Map<String, List<Person>> byGroup = people.stream()
.collect(Collectors.groupingBy(p ->
p.getAge() < 30 ? "青年" : "中年"));
groupingBy根据分类函数将元素分组到不同键下。
9. 链式操作:筛选、排序、去重、限制数量
List<Person> result = people.stream()
.filter(p -> p.getAge() > 20) // 筛选
.sorted(Comparator.comparing(Person::getAge).reversed()) // 按年龄降序
.distinct() // 去重(需 Person 重写 equals/hashCode)
.limit(2) // 取前2个
.collect(Collectors.toList());
distinct()依赖元素的equals()方法判断重复。limit(n)限制结果数量,常用于分页或取 Top N。
注意事项与最佳实践
| 场景 | 推荐做法 | 避免做法 |
|---|---|---|
| 修改原始集合 | 不要在流操作中修改源头集合 | 在 forEach 中直接修改 people 列表 |
| 空值处理 | 使用 filter(Objects::nonNull) 提前过滤 |
直接调用可能为 null 的对象方法 |
| 性能敏感场景 | 对大集合使用 parallelStream()(谨慎) |
盲目并行化导致线程开销过大 |
| 复杂逻辑 | 将 Lambda 拆分为独立方法并用方法引用 | 在 Lambda 内写多行复杂逻辑 |
- 不要在流操作中产生副作用(如修改外部变量),这会破坏函数式编程的纯度,导致并发问题。
- 优先使用方法引用(如
String::length)而非显式 Lambda,提升可读性。 - 终端操作只能调用一次,多次调用会抛出异常。若需多次使用结果,应先
collect到集合中。
// 错误示例:同一个流多次终端操作
Stream<String> stream = people.stream().map(Person::getName);
stream.forEach(System.out::println);
stream.count(); // 抛出 IllegalStateException
正确做法是每次重新创建流,或先收集结果:
List<String> names = people.stream().map(Person::getName).collect(Collectors.toList());
names.forEach(System.out::println);
System.out.println("总数: " + names.size());
实战示例:处理订单数据
假设有订单类:
class Order {
private String customer;
private double amount;
private boolean paid;
// 构造函数与 getter 省略
}
需求:找出已支付订单中金额大于 100 的客户名,去重后按字母排序。
List<Order> orders = Arrays.asList(
new Order("Alice", 120, true),
new Order("Bob", 80, true),
new Order("Alice", 150, true),
new Order("Charlie", 200, false)
);
List<String> customers = orders.stream()
.filter(Order::isPaid) // 已支付
.filter(o -> o.getAmount() > 100) // 金额 > 100
.map(Order::getCustomer) // 提取客户名
.distinct() // 去重
.sorted() // 字母排序
.collect(Collectors.toList());
最终 customers 包含 ["Alice"],因为 Bob 金额不足,Charlie 未支付。
使用 Stream.of() 创建流
除了集合,还可以从单个值或数组创建流:
Stream<String> stream1 = Stream.of("a", "b", "c");
int[] nums = {1, 2, 3};
IntStream stream2 = Arrays.stream(nums);
处理文件行
try (Stream<String> lines = Files.lines(Paths.get("data.txt"))) {
long count = lines.filter(line -> line.contains("ERROR"))
.count();
} catch (IOException e) {
e.printStackTrace();
}
Files.lines()返回Stream<String>,自动管理资源(需 try-with-resources)。
Stream API 让集合处理变得像 SQL 查询一样直观。掌握 filter、map、collect、sorted 等核心操作,结合 Lambda 表达式,即可写出简洁高效的代码。记住:流是一次性管道,操作不可逆,设计时应以数据转换为目标,而非状态变更。

暂无评论,快来抢沙发吧!