Skip to content
0

Java Stream 流详解 ​

什么是 Stream ​

Stream 是 Java 8 引入的一个重要的新特性,它提供了一种函数式、声明式的方式来处理集合数据。Stream 不是一种数据结构,而是对数据源(集合、数组等)进行高级迭代和聚合操作的抽象。

Stream 操作的特点:

  • 不存储数据:Stream 本身不存储元素,只是对数据源进行计算
  • 不改变源数据:Stream 操作不会修改原始数据源,而是返回一个新的结果
  • 惰性求值:中间操作不会立即执行,只有在遇到终止操作时才会触发计算
  • 可消费性:Stream 只能被消费一次,一旦执行了终止操作,Stream 就会关闭

Stream 的获取方式 ​

1. 通过 Collection 获取 ​

单列集合(List、Set 等)可以通过 stream() 或 parallelStream() 方法获取 Stream:

java
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    Collections.addAll(list, "1", "2", "3");

    // 获取顺序流
    list.stream().forEach(s -> System.out.println(s));

    // 获取并行流(适合大数据量并行处理)
    list.parallelStream().forEach(s -> System.out.println(s));
}

2. 通过 Map 获取 ​

双列集合无法直接使用 Stream,需要先将 Map 转换为单列集合:

java
public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<>();
    map.put("Alice", 25);
    map.put("Bob", 30);

    // 通过 keySet() 获取键的 Stream
    map.keySet().stream().forEach(System.out::println);

    // 通过 values() 获取值的 Stream
    map.values().stream().forEach(System.out::println);

    // 通过 entrySet() 获取键值对的 Stream(最常用)
    map.entrySet().stream()
       .filter(entry -> entry.getValue() > 25)
       .forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));
}

3. 通过数组获取 ​

java
public static void main(String[] args) {
    String[] arr = {"a", "b", "c"};

    // 使用 Arrays.stream()
    Arrays.stream(arr).forEach(System.out::println);

    // 获取数组的部分范围
    Arrays.stream(arr, 0, 2).forEach(System.out::println); // 输出 a, b
}

4. 通过 Stream 的静态方法获取 ​

java
public static void main(String[] args) {
    // 零散数据
    Stream.of("a", "b", "c", "d").forEach(System.out::println);

    // 空 Stream
    Stream.empty();

    // 通过 Builder 构建
    Stream.Builder<String> builder = Stream.builder();
    builder.add("a").add("b").add("c");
    Stream<String> stream = builder.build();
}

5. 通过生成函数获取 ​

java
public static void main(String[] args) {
    // 迭代生成(从 0 开始,每次 +2)
    Stream.iterate(0, n -> n + 2)
          .limit(5)
          .forEach(System.out::println); // 0, 2, 4, 6, 8

    // 生成无限随机数
    Stream.generate(Math::random)
          .limit(5)
          .forEach(System.out::println);

    // IntStream 范围生成(包含起始值,不包含结束值)
    IntStream.range(1, 5).forEach(System.out::println);      // 1, 2, 3, 4
    IntStream.rangeClosed(1, 5).forEach(System.out::println); // 1, 2, 3, 4, 5
}

Stream 的中间操作 ​

中间操作返回一个新的 Stream,可以链式调用。这些操作是惰性的,不会立即执行。

筛选与切片 ​

java
public static void main(String[] args) {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    // filter:按条件过滤
    numbers.stream()
           .filter(n -> n % 2 == 0)
           .forEach(System.out::println); // 2, 4, 6, 8, 10

    // limit:截断流,只取前 N 个元素
    numbers.stream()
           .limit(3)
           .forEach(System.out::println); // 1, 2, 3

    // skip:跳过前 N 个元素
    numbers.stream()
           .skip(5)
           .forEach(System.out::println); // 6, 7, 8, 9, 10

    // distinct:去重(基于 equals 和 hashCode)
    Arrays.asList(1, 2, 2, 3, 3, 3).stream()
          .distinct()
          .forEach(System.out::println); // 1, 2, 3
}

映射 ​

java
public static void main(String[] args) {
    List<String> names = Arrays.asList("alice", "bob", "charlie");

    // map:将每个元素转换为另一种类型
    names.stream()
         .map(String::toUpperCase)
         .forEach(System.out::println); // ALICE, BOB, CHARLIE

    // mapToInt / mapToLong / mapToDouble:转换为基本类型流
    List<String> numStrs = Arrays.asList("1", "2", "3");
    int sum = numStrs.stream()
                     .mapToInt(Integer::parseInt)
                     .sum(); // 6

    // flatMap:将每个元素转换为 Stream,然后扁平化为一个 Stream
    List<List<Integer>> nestedList = Arrays.asList(
        Arrays.asList(1, 2),
        Arrays.asList(3, 4),
        Arrays.asList(5, 6)
    );
    nestedList.stream()
              .flatMap(List::stream)
              .forEach(System.out::println); // 1, 2, 3, 4, 5, 6
}

排序 ​

java
public static void main(String[] args) {
    List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);

    // sorted():自然排序(元素需实现 Comparable)
    numbers.stream()
           .sorted()
           .forEach(System.out::println); // 1, 1, 2, 3, 4, 5, 6, 9

    // sorted(Comparator):自定义排序
    numbers.stream()
           .sorted((a, b) -> b - a) // 降序
           .forEach(System.out::println); // 9, 6, 5, 4, 3, 2, 1, 1

    // 更简洁的 Comparator 写法
    numbers.stream()
           .sorted(Comparator.reverseOrder())
           .forEach(System.out::println);
}

peek:调试与观察 ​

java
public static void main(String[] args) {
    // peek 通常用于调试,可以查看流中的元素而不影响处理
    List<Integer> result = Arrays.asList(1, 2, 3, 4, 5).stream()
        .peek(n -> System.out.println("过滤前: " + n))
        .filter(n -> n > 2)
        .peek(n -> System.out.println("过滤后: " + n))
        .map(n -> n * 10)
        .peek(n -> System.out.println("映射后: " + n))
        .collect(Collectors.toList());
    // 输出: 过滤前: 1
    //       过滤前: 2
    //       过滤前: 3
    //       过滤后: 3
    //       映射后: 30
    //       ...
}

Stream 的终止操作 ​

终止操作会触发实际的计算,并产生结果或副作用。执行后 Stream 将关闭。

遍历与匹配 ​

java
public static void main(String[] args) {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

    // forEach:遍历每个元素
    numbers.stream().forEach(System.out::println);

    // allMatch:是否所有元素都满足条件
    boolean allPositive = numbers.stream().allMatch(n -> n > 0); // true

    // anyMatch:是否存在元素满足条件
    boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0); // true

    // noneMatch:是否没有元素满足条件
    boolean noNegative = numbers.stream().noneMatch(n -> n < 0); // true

    // findFirst:返回第一个元素(通常与 filter 配合使用)
    Optional<Integer> first = numbers.stream().findFirst();

    // findAny:返回任意一个元素(并行流时性能更好)
    Optional<Integer> any = numbers.stream().findAny();
}

聚合计算 ​

java
public static void main(String[] args) {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

    // count:元素个数
    long count = numbers.stream().count(); // 5

    // max / min:最大/最小值(返回 Optional)
    Optional<Integer> max = numbers.stream().max(Integer::compareTo); // 5
    Optional<Integer> min = numbers.stream().min(Integer::compareTo); // 1

    // reduce:归约操作,将流中的元素合并为一个结果
    // 求和
    int sum = numbers.stream().reduce(0, (a, b) -> a + b); // 15
    // 或更简洁的写法
    int sum2 = numbers.stream().reduce(0, Integer::sum); // 15

    // 无初始值的 reduce(返回 Optional)
    Optional<Integer> sumOpt = numbers.stream().reduce(Integer::sum); // 15

    // 求乘积
    int product = numbers.stream().reduce(1, (a, b) -> a * b); // 120
}

收集结果(Collect) ​

collect 是最强大的终止操作之一,通常与 Collectors 工具类配合使用:

java
public static void main(String[] args) {
    List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");

    // 收集为 List
    List<String> list = names.stream().collect(Collectors.toList());

    // 收集为 Set
    Set<String> set = names.stream().collect(Collectors.toSet());

    // 收集为指定类型的集合
    LinkedList<String> linkedList = names.stream()
        .collect(Collectors.toCollection(LinkedList::new));

    // 收集为 Map(需要处理键冲突)
    Map<String, Integer> nameLengthMap = names.stream()
        .collect(Collectors.toMap(
            name -> name,
            String::length,
            (existing, replacement) -> existing // 处理键冲突
        ));

    // joining:拼接字符串
    String joined = names.stream().collect(Collectors.joining(", ")); // Alice, Bob, Charlie, David, Eve
    String joinedWithPrefixSuffix = names.stream()
        .collect(Collectors.joining(", ", "[", "]")); // [Alice, Bob, Charlie, David, Eve]

    // groupingBy:分组
    Map<Integer, List<String>> groupedByLength = names.stream()
        .collect(Collectors.groupingBy(String::length));
    // {3=[Bob, Eve], 5=[Alice, David], 7=[Charlie]}

    // partitioningBy:分区(按条件分为两组)
    Map<Boolean, List<String>> partitioned = names.stream()
        .collect(Collectors.partitioningBy(name -> name.length() > 3));
    // {false=[Bob, Eve], true=[Alice, Charlie, David]}

    // 统计信息
    IntSummaryStatistics stats = names.stream()
        .collect(Collectors.summarizingInt(String::length));
    System.out.println(stats.getAverage()); // 平均长度
    System.out.println(stats.getMax());     // 最大长度
    System.out.println(stats.getCount());   // 元素个数
}

并行流(Parallel Stream) ​

java
public static void main(String[] args) {
    List<Integer> numbers = new ArrayList<>();
    for (int i = 1; i <= 1000000; i++) {
        numbers.add(i);
    }

    // 获取并行流
    long sum = numbers.parallelStream()
                      .mapToLong(Integer::longValue)
                      .sum();

    // 或将普通流转换为并行流
    long sum2 = numbers.stream()
                       .parallel()
                       .mapToLong(Integer::longValue)
                       .sum();
}

注意:并行流并非总是更快。以下情况不建议使用并行流:

  • 数据量较小(通常小于 10,000)
  • 数据源分割成本较高(如 LinkedList、Stream.iterate)
  • 需要保证操作顺序(使用 forEachOrdered 性能较差)
  • 涉及线程不安全的操作

常用示例 ​

对象列表处理 ​

java
class Person {
    String name;
    int age;
    String department;
    double salary;
    // 构造函数、getter...
}

public static void main(String[] args) {
    List<Person> employees = Arrays.asList(
        new Person("Alice", 30, "IT", 8000),
        new Person("Bob", 25, "HR", 5000),
        new Person("Charlie", 35, "IT", 12000),
        new Person("David", 28, "Finance", 7000)
    );

    // 1. 按部门分组,计算平均工资
    Map<String, Double> avgSalaryByDept = employees.stream()
        .collect(Collectors.groupingBy(
            Person::getDepartment,
            Collectors.averagingDouble(Person::getSalary)
        ));

    // 2. 找出每个部门工资最高的人
    Map<String, Optional<Person>> topByDept = employees.stream()
        .collect(Collectors.groupingBy(
            Person::getDepartment,
            Collectors.maxBy(Comparator.comparingDouble(Person::getSalary))
        ));

    // 3. 获取年龄大于 28 岁的员工姓名,按工资降序排列
    List<String> names = employees.stream()
        .filter(p -> p.getAge() > 28)
        .sorted(Comparator.comparingDouble(Person::getSalary).reversed())
        .map(Person::getName)
        .collect(Collectors.toList());

    // 4. 获取所有部门名称(去重)
    List<String> departments = employees.stream()
        .map(Person::getDepartment)
        .distinct()
        .collect(Collectors.toList());
}

总结 ​

操作类型常用方法说明
筛选filter, distinct, limit, skip过滤和截取元素
映射map, flatMap, mapToInt转换元素类型或结构
排序sorted对元素进行排序
消费forEach, peek遍历或观察元素
匹配allMatch, anyMatch, noneMatch条件判断
查找findFirst, findAny查找元素
聚合reduce, count, max, min归约计算
收集collect将结果收集到集合中
最近更新