Java 8 Stream(保姆级教程)

Java 8 Stream:让集合操作变得优雅而高效

在 Java 8 发布之前,处理集合数据通常需要编写大量冗长的 for 循环代码。无论是过滤、映射、聚合还是分组,代码往往又长又难读。而 Java 8 引入的 Stream API,就像为集合操作打开了一扇全新的窗户——它不仅让代码更简洁,还支持并行处理,极大提升了开发效率和性能表现。

Stream 不是新的数据结构,而是一种对集合数据的“管道式”处理方式。你可以把它想象成一条流水线:数据从源头(如 List、Set)流入,经过一系列处理步骤(过滤、转换、聚合等),最终输出结果。整个过程是函数式风格的,强调“做什么”而不是“怎么做”。

本文将带你一步步理解 Java 8 Stream 的核心概念,从基础语法到高级用法,结合实际案例,帮助你真正掌握这一现代 Java 编程的核心技能。


创建数组与初始化

在使用 Stream 之前,我们需要先创建一个数据源。Java 8 提供了多种方式来创建集合,方便后续使用 Stream 进行操作。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

// 1. 从数组创建 Stream
String[] names = {"Alice", "Bob", "Charlie", "Diana"};
Stream<String> nameStream = Arrays.stream(names);

// 2. 从 List 创建 Stream
List<String> nameList = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
Stream<String> listStream = nameList.stream();

// 3. 使用 Stream.of() 方法创建
Stream<String> ofStream = Stream.of("Alice", "Bob", "Charlie", "Diana");

// 4. 从集合创建(如 Set)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> numberStream = numbers.stream();

注释说明:

  • Arrays.stream():将数组转换为 Stream,适用于任何类型的数组。
  • List.stream():集合类自带的方法,直接返回 Stream。
  • Stream.of():静态工厂方法,适合少量元素的快速创建。
  • 所有 Stream 操作都是惰性的,即只有调用终端操作(如 collectforEach)时才会真正执行。

常用中间操作:过滤与映射

中间操作是 Stream 流程中的“加工环节”。它们不会立即执行,而是构建一个处理链,直到遇到终端操作才触发计算。

过滤数据:filter

假设我们有一个学生名单,只想找出年龄大于 18 岁的学生。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public String toString() {
        return "Student{name='" + name + "', age=" + age + "}";
    }
}

// 创建学生列表
List<Student> students = Arrays.asList(
    new Student("张三", 17),
    new Student("李四", 20),
    new Student("王五", 18),
    new Student("赵六", 22)
);

// 使用 filter 过滤年龄大于 18 的学生
List<Student> adults = students.stream()
    .filter(student -> student.getAge() > 18)  // 仅保留年龄 > 18 的学生
    .collect(Collectors.toList());

// 输出结果
adults.forEach(System.out::println);

注释说明:

  • filter() 接收一个 Predicate(函数式接口),返回 true 的元素才会保留。
  • student -> student.getAge() > 18 是 Lambda 表达式,简洁明了。
  • collect(Collectors.toList()) 是终端操作,将 Stream 结果收集为 List。

映射数据:map

如果我们要从学生对象中提取姓名,或者将名字转为大写,就可以使用 map

// 将学生列表中的姓名提取为字符串列表
List<String> names = students.stream()
    .map(Student::getName)  // 使用方法引用,等价于 student -> student.getName()
    .collect(Collectors.toList());

System.out.println(names); // 输出:[张三, 李四, 王五, 赵六]

// 将姓名转为大写
List<String> upperNames = students.stream()
    .map(student -> student.getName().toUpperCase())
    .collect(Collectors.toList());

System.out.println(upperNames); // 输出:[张三, 李四, 王五, 赵六](注意:中文无大小写)

注释说明:

  • map() 用于将每个元素转换为另一种类型。
  • Student::getName 是方法引用,语法更简洁。
  • 可以链式调用多个中间操作,形成处理流水线。

聚合与统计:reduce 与 collect

当需要对数据进行汇总或统计时,reducecollect 是两个强大的工具。

reduce:逐步聚合

reduce 用于将 Stream 中的所有元素通过一个函数逐步合并为一个结果。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 求和:从 0 开始,每次加一个数
Integer sum = numbers.stream()
    .reduce(0, (a, b) -> a + b);  // 初始值 0,累加函数

System.out.println("总和:" + sum); // 输出:总和:15

// 求最大值(也可以用 max)
Integer max = numbers.stream()
    .reduce(Integer.MIN_VALUE, (a, b) -> a > b ? a : b);

System.out.println("最大值:" + max); // 输出:最大值:5

注释说明:

  • reduce(0, (a, b) -> a + b):第一个参数是初始值,第二个是二元操作函数。
  • 适合实现自定义聚合逻辑,但对简单场景,Collectors.summingInt 更推荐。

collect:收集结果

collect 是最常用的终端操作,配合 Collectors 工具类,可以实现复杂的数据收集。

// 按年龄分组
Map<Integer, List<Student>> groupedByAge = students.stream()
    .collect(Collectors.groupingBy(Student::getAge));

groupedByAge.forEach((age, list) -> 
    System.out.println("年龄 " + age + ":" + list));

// 统计学生数量
Long count = students.stream()
    .collect(Collectors.counting());

System.out.println("学生总数:" + count); // 输出:学生总数:4

// 求平均年龄
Double avgAge = students.stream()
    .collect(Collectors.averagingInt(Student::getAge));

System.out.println("平均年龄:" + avgAge); // 输出:平均年龄:19.5

注释说明:

  • groupingBy:按指定属性分组,返回 Map。
  • counting():统计元素个数。
  • averagingInt:计算整数字段的平均值。

并行处理:提升性能的关键

在处理大量数据时,串行处理可能较慢。Java 8 Stream 支持并行流,通过 parallelStream() 能够自动利用多核 CPU。

List<Integer> largeList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 串行处理
long serialTime = System.nanoTime();
long serialSum = largeList.stream()
    .mapToInt(Integer::intValue)
    .sum();
long serialDuration = System.nanoTime() - serialTime;

// 并行处理
long parallelTime = System.nanoTime();
long parallelSum = largeList.parallelStream()
    .mapToInt(Integer::intValue)
    .sum();
long parallelDuration = System.nanoTime() - parallelTime;

System.out.println("串行耗时:" + serialDuration + " ns");
System.out.println("并行耗时:" + parallelDuration + " ns");
System.out.println("结果一致:" + (serialSum == parallelSum));

注释说明:

  • parallelStream() 创建并行流,内部会使用 ForkJoinPool 线程池。
  • 适合计算密集型任务,如大数据聚合、复杂过滤。
  • 注意:并行流不保证顺序,若需保持顺序,可用 unordered()sorted()

实际应用场景:电商订单处理

我们来看一个真实场景:处理订单列表,筛选出金额大于 100 元的订单,计算总金额,并按用户分组。

class Order {
    private String userId;
    private double amount;

    public Order(String userId, double amount) {
        this.userId = userId;
        this.amount = amount;
    }

    public String getUserId() { return userId; }
    public double getAmount() { return amount; }

    @Override
    public String toString() {
        return "Order{userId='" + userId + "', amount=" + amount + "}";
    }
}

List<Order> orders = Arrays.asList(
    new Order("U001", 50.0),
    new Order("U002", 150.0),
    new Order("U001", 80.0),
    new Order("U003", 200.0),
    new Order("U002", 120.0)
);

// 1. 过滤金额 > 100 的订单
List<Order> highValueOrders = orders.stream()
    .filter(order -> order.getAmount() > 100)
    .collect(Collectors.toList());

// 2. 计算总金额
double total = highValueOrders.stream()
    .mapToDouble(Order::getAmount)
    .sum();

// 3. 按用户分组并统计金额
Map<String, Double> userTotal = orders.stream()
    .collect(Collectors.groupingBy(
        Order::getUserId,
        Collectors.summingDouble(Order::getAmount)
    ));

System.out.println("高价值订单:" + highValueOrders);
System.out.println("总金额:" + total);
System.out.println("用户金额汇总:" + userTotal);

注释说明:

  • 这个例子展示了 Stream 在业务逻辑中的典型应用:过滤、聚合、分组。
  • Collectors.groupingBy 支持嵌套收集器,可直接计算分组后的总和。
  • 代码清晰、可读性强,维护成本低。

小结

Java 8 Stream 是 Java 语言现代化的重要一步,它将函数式编程思想融入 Java,让集合操作更简洁、高效、可读。通过 filtermapreducecollect 等操作,我们可以像搭积木一样构建复杂的数据处理流程。

掌握 Stream,不仅能写出更优雅的代码,还能提升开发效率,尤其在处理大数据量或复杂业务逻辑时优势明显。虽然初学者可能需要一点时间适应,但一旦熟悉,就会发现它远比传统的 for 循环强大。

建议从简单场景开始练习,逐步掌握链式调用、并行流、分组统计等高级用法。相信在不久的将来,你也会像老手一样,随手写出一行 Stream 代码,优雅地解决复杂问题。