文章目录

Java 流处理:Stream API 与 Lambda 表达式

发布于 2026-04-03 20:28:20 · 浏览 2 次 · 评论 0 条

Java 流处理:Stream API 与 Lambda 表达式

Java 8 引入了 Stream API 和 Lambda 表达式,彻底改变了处理集合数据的方式。传统 for 循环遍历、筛选、转换数据的代码往往冗长且难以维护,而 Stream 提供了一种声明式、链式调用的风格,让逻辑更清晰、代码更简洁。


准备工作:理解核心概念

Lambda 表达式是一种匿名函数,用于替代匿名内部类,尤其适用于函数式接口(只有一个抽象方法的接口)。它的基本语法是 (参数) -> 表达式(参数) -> { 语句块 }

Stream(流)不是数据结构,而是对数据源(如 List、Set、数组)的一系列操作管道。Stream 操作分为两类:

  • 中间操作(如 filtermap):返回新的 Stream,支持链式调用。
  • 终端操作(如 collectforEach):触发实际计算并结束流。

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 查询一样直观。掌握 filtermapcollectsorted 等核心操作,结合 Lambda 表达式,即可写出简洁高效的代码。记住:流是一次性管道,操作不可逆,设计时应以数据转换为目标,而非状态变更。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文