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 ──→ ... ──→ 终止操作| 特性 | Collection | Stream |
|---|---|---|
| 存储数据 | ✅ | ❌ |
| 遍历次数 | 多次 | 单次 |
| 目的 | 存储 | 计算 |
| 惰性执行 | 不适用 | ✅ |
| 并行 | 手动 | 自动 |
两种操作
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 = 数据源 + 中间操作(多个) + 终止操作(一个)