Skip to content

Stream 核心概念

为什么需要 Stream?

想象一下这个场景:你有一个列表,要找出年龄大于 20 岁的用户,再按姓名排序,最后取前 5 个。

不用 Stream:

java
List<User> filtered = new ArrayList<>();
for (User u : users) {
    if (u.getAge() > 20) {
        filtered.add(u);
    }
}
Collections.sort(filtered, new Comparator<User>() {
    @Override
    public int compare(User a, User b) {
        return a.getName().compareTo(b.getName());
    }
});
List<User> result = filtered.subList(0, Math.min(5, filtered.size()));

用 Stream:

java
List<User> result = users.stream()
    .filter(u -> u.getAge() > 20)
    .sorted(Comparator.comparing(User::getName))
    .limit(5)
    .collect(Collectors.toList());

10 行变 4 行。这就是 Stream 的意义。

Stream 是什么

Stream 不是数据结构。它不存储数据,只是一个惰性计算管道

数据源 ──→ 中间操作1 ──→ 中间操作2 ──→ ... ──→ 终止操作
特性CollectionStream
存储数据
遍历次数多次单次
目的存储计算
惰性执行不适用
并行手动自动

两种操作

Stream 的操作分为两类:中间操作终止操作

中间操作(Lazy)

调用中间操作,返回的还是 Stream,所以可以链式调用。但此时什么都不会执行。

java
// 这里不会打印任何东西——只是声明了一个计算管道
stream
    .filter(s -> {
        System.out.println("filter: " + s);  // 不会执行
        return s.length() > 3;
    })
    .map(s -> {
        System.out.println("map: " + s);  // 不会执行
        return s.toUpperCase();
    });

终止操作(Terminal)

终止操作触发整个管道的执行,每个 Stream 只能调用一次终止操作。

java
// 加了 forEach() 之后,前面的 filter 和 map 才会真正执行
stream
    .filter(s -> {
        System.out.println("filter: " + s);
        return s.length() > 3;
    })
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .forEach(s -> System.out.println("result: " + s));

创建 Stream

从集合

java
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> s1 = list.stream();      // 串行
Stream<String> s2 = list.parallelStream();  // 并行

从数组

java
String[] arr = {"a", "b", "c"};
Stream<String> s1 = Arrays.stream(arr);
Stream<String> s2 = Stream.of("a", "b", "c");

其他方式

java
// 空流
Stream.empty();

// 固定值
Stream.of("a", "b", "c");

// 迭代:生成无限序列
Stream.iterate(0, n -> n + 2)  // 0, 2, 4, 6, ...
    .limit(10);

// 生成:每次调用生成一个新值
Stream.generate(Math::random)
    .limit(5);

// 基本类型专用流,避免装箱开销
IntStream.range(1, 100);         // 1 到 99
LongStream.rangeClosed(1, 100);  // 1 到 100(含)

常用操作分类

┌──────────────────────────────────────────────────────────┐
│  操作类型           具体操作                              │
├──────────────────────────────────────────────────────────┤
│  筛选 (Filtering)   filter, distinct, limit, skip        │
│  映射 (Mapping)     map, flatMap, mapToInt...            │
│  排序 (Sorting)     sorted, sorted(Comparator)           │
│  查找 (Finding)     findFirst, findAny, anyMatch...      │
│  归约 (Reducing)    reduce, collect, forEach            │
└──────────────────────────────────────────────────────────┘

筛选操作

java
// filter:按条件过滤
list.stream()
    .filter(s -> s.length() > 3)
    .collect(Collectors.toList());

// distinct:去重(依赖 equals)
list.stream()
    .distinct()
    .collect(Collectors.toList());

// limit:取前 n 个
list.stream()
    .limit(10)
    .collect(Collectors.toList());

// skip:跳过前 n 个
list.stream()
    .skip(5)
    .collect(Collectors.toList());

映射操作

java
// map:转换类型
list.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());

// mapToInt:转数值流(避免装箱)
list.stream()
    .mapToInt(String::length)
    .sum();

// flatMap:扁平化
// 把嵌套的列表展开成一层的
List<List<String>> nested = Arrays.asList(
    Arrays.asList("a", "b"),
    Arrays.asList("c", "d")
);
List<String> flat = nested.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toList());
// [a, b, c, d]

排序操作

java
// 自然排序(元素需实现 Comparable)
list.stream()
    .sorted()
    .collect(Collectors.toList());

// 自定义排序
list.stream()
    .sorted(Comparator.comparingInt(String::length))
    .collect(Collectors.toList());

// 降序
list.stream()
    .sorted(Comparator.comparingInt(String::length).reversed())
    .collect(Collectors.toList());

// 多条件排序
people.stream()
    .sorted(Comparator
        .comparing(Person::getAge)
        .thenComparing(Person::getName))
    .collect(Collectors.toList());

查找与匹配

java
// anyMatch:任意一个匹配
boolean hasAdult = people.stream()
    .anyMatch(p -> p.getAge() > 18);

// allMatch:全部匹配
boolean allAdult = people.stream()
    .allMatch(p -> p.getAge() > 18);

// noneMatch:都不匹配
boolean noneChild = people.stream()
    .noneMatch(p -> p.getAge() < 18);

// findFirst:找第一个
Optional<Person> first = people.stream()
    .filter(p -> p.getAge() > 20)
    .findFirst();

// findAny:找任意一个(并行时可能比 findFirst 快)
Optional<Person> any = people.stream()
    .filter(p -> p.getAge() > 20)
    .findAny();

终止操作详解

collect — 收集到容器

java
List<String> list = names.stream()
    .filter(s -> s.length() > 3)
    .collect(Collectors.toList());

Set<String> set = names.stream()
    .filter(s -> s.length() > 3)
    .collect(Collectors.toSet());

Map<String, Integer> map = names.stream()
    .filter(s -> s.length() > 3)
    .collect(Collectors.toMap(
        s -> s,             // key
        String::length      // value
    ));

// joining — 拼接字符串
String joined = names.stream()
    .filter(s -> s.length() > 3)
    .collect(Collectors.joining(", "));

// groupingBy — 分组
Map<Integer, List<Person>> byAge = people.stream()
    .collect(Collectors.groupingBy(Person::getAge));

// partitioningBy — 按条件分区(true/false 两组)
Map<Boolean, List<Person>> byAdult = people.stream()
    .collect(Collectors.partitioningBy(p -> p.getAge() > 18));

reduce — 归约

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

// 无初始值:返回 Optional
Optional<Integer> sum = nums.stream()
    .reduce((a, b) -> a + b);
sum.ifPresent(System.out::println);  // 15

// 有初始值
int result = nums.stream()
    .reduce(0, Integer::sum);  // 15

// 求最大值/最小值
Optional<Integer> max = nums.stream()
    .reduce(Integer::max);

forEach — 遍历

java
// 普通遍历(不保证顺序)
list.stream().forEach(System.out::println);

// 保证顺序(并行流时有用)
list.stream().forEachOrdered(System.out::println);

短路操作

有些操作不需要处理完所有元素就能返回结果:

java
// limit:找到 10 个就停
list.stream()
    .filter(n -> n > 0)
    .limit(10)
    .collect(Collectors.toList());

// findFirst:找到第一个就停
Optional<String> first = list.stream()
    .filter(s -> s.length() > 5)
    .findFirst();

// anyMatch:找到一个匹配就停
boolean found = list.stream()
    .anyMatch(s -> s.startsWith("A"));

惰性求值的好处

中间操作是惰性的,这意味着只有终止操作时才真正执行。这带来一个好处:短路

java
// 即使列表有 100 万个元素,也只执行到找到第一个就停
Optional<String> firstLong = list.stream()
    .filter(s -> s.length() > 100)
    .map(s -> {
        System.out.println("map: " + s);  // 最多执行一次
        return s;
    })
    .findFirst();

小结

Stream 的核心要点:

  • 惰性求值:中间操作不执行,只有终止操作触发
  • 管道式:多个操作可以链式组合
  • 短路:部分操作不需要处理全部数据
  • 一次性:一个 Stream 只能消费一次

记住这个公式:

Stream = 数据源 + 中间操作(多个) + 终止操作(一个)

基于 VitePress 构建