Appearance
第33课:Stream API
🎯 学习目标
- 理解 Stream 的概念和特点
- 掌握 Stream 的创建方式
- 掌握中间操作(filter、map、sorted等)
- 掌握终止操作(collect、reduce等)
- 理解并行流的使用
📖 一、Stream 是什么?
传统 vs Stream
场景:过滤并转换列表
java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
// 传统写法
List<String> result = new ArrayList<>();
for (String name : names) {
if (name.length() > 3) {
result.add(name.toUpperCase());
}
}
// Stream 写法
List<String> result = names.stream()
.filter(name -> name.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());Stream 特点
- 不是数据结构 - 不存储数据,只是对数据源的视图
- 惰性求值 - 中间操作不会立即执行
- 一次性 - 每个 Stream 只能使用一次
- 支持链式调用 - 流式风格
java
// 惰性求值示例
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream()
.filter(n -> {
System.out.println("filter: " + n);
return n > 2;
})
.map(n -> {
System.out.println("map: " + n);
return n * 2;
});
// 此时还没有输出(惰性求值)
List<Integer> result = stream.collect(Collectors.toList());
// 现在才执行,输出:
// filter: 1
// filter: 2
// filter: 3
// map: 3
// filter: 4
// map: 4
// filter: 5
// map: 5📖 二、创建 Stream
1. 从集合创建
java
List<String> list = Arrays.asList("A", "B", "C");
Stream<String> stream = list.stream();
Set<String> set = new HashSet<>(list);
Stream<String> stream2 = set.stream();
Map<String, Integer> map = new HashMap<>();
Stream<Map.Entry<String, Integer>> stream3 = map.entrySet().stream();2. 从数组创建
java
String[] array = {"A", "B", "C"};
Stream<String> stream = Arrays.stream(array);
// 指定范围
Stream<String> stream2 = Arrays.stream(array, 0, 2); // A, B3. Stream.of()
java
Stream<String> stream = Stream.of("A", "B", "C");
Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5);4. 无限流
java
// generate - 生成无限流
Stream<Double> randomStream = Stream.generate(Math::random);
randomStream.limit(10).forEach(System.out::println); // 取前10个
// iterate - 迭代生成
Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2);
evenNumbers.limit(10).forEach(System.out::println); // 0, 2, 4, ..., 185. 其他方式
java
// 空流
Stream<String> empty = Stream.empty();
// IntStream、LongStream、DoubleStream
IntStream intStream = IntStream.range(1, 11); // 1到10
LongStream longStream = LongStream.rangeClosed(1, 10); // 1到10(包含10)
// 文件行
Stream<String> lines = Files.lines(Paths.get("file.txt"));📖 三、中间操作
1. filter - 过滤
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 过滤偶数
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// [2, 4, 6, 8, 10]2. map - 转换
java
List<String> names = Arrays.asList("alice", "bob", "charlie");
// 转换为大写
List<String> upperNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// [ALICE, BOB, CHARLIE]
// 提取长度
List<Integer> lengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
// [5, 3, 7]3. flatMap - 扁平化
java
List<List<Integer>> listOfLists = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5),
Arrays.asList(6, 7, 8, 9)
);
// 扁平化为一个流
List<Integer> flatList = listOfLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 实际应用:处理字符串
List<String> words = Arrays.asList("Hello", "World");
List<String> letters = words.stream()
.flatMap(word -> Arrays.stream(word.split("")))
.collect(Collectors.toList());
// [H, e, l, l, o, W, o, r, l, d]4. distinct - 去重
java
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 5);
List<Integer> unique = numbers.stream()
.distinct()
.collect(Collectors.toList());
// [1, 2, 3, 4, 5]5. sorted - 排序
java
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
// 自然排序
List<Integer> sorted = numbers.stream()
.sorted()
.collect(Collectors.toList());
// [1, 2, 5, 8, 9]
// 自定义排序
List<Integer> sortedDesc = numbers.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
// [9, 8, 5, 2, 1]
// 对象排序
List<Person> people = ...;
List<Person> sortedPeople = people.stream()
.sorted(Comparator.comparing(Person::getAge))
.collect(Collectors.toList());6. limit 和 skip
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 取前5个
List<Integer> first5 = numbers.stream()
.limit(5)
.collect(Collectors.toList());
// [1, 2, 3, 4, 5]
// 跳过前5个
List<Integer> after5 = numbers.stream()
.skip(5)
.collect(Collectors.toList());
// [6, 7, 8, 9, 10]
// 分页:跳过前10个,取5个
List<Integer> page = numbers.stream()
.skip(10)
.limit(5)
.collect(Collectors.toList());7. peek - 调试
java
// peek 不改变流,用于调试
List<Integer> result = numbers.stream()
.filter(n -> n > 5)
.peek(n -> System.out.println("Filtered: " + n))
.map(n -> n * 2)
.peek(n -> System.out.println("Mapped: " + n))
.collect(Collectors.toList());📖 四、终止操作
1. collect - 收集
java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 转为 List
List<String> list = names.stream().collect(Collectors.toList());
// 转为 Set
Set<String> set = names.stream().collect(Collectors.toSet());
// 转为 Map
Map<String, Integer> map = names.stream()
.collect(Collectors.toMap(
name -> name, // key
String::length // value
));
// {Alice=5, Bob=3, Charlie=7}
// 拼接字符串
String joined = names.stream()
.collect(Collectors.joining(", "));
// "Alice, Bob, Charlie"
// 分组
List<Person> people = ...;
Map<String, List<Person>> byCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity));
// 分区(true/false 分组)
Map<Boolean, List<Integer>> partition = numbers.stream()
.collect(Collectors.partitioningBy(n -> n > 5));
// {false=[1,2,3,4,5], true=[6,7,8,9,10]}2. forEach - 遍历
java
names.stream().forEach(System.out::println);
// forEachOrdered - 保证顺序(并行流中)
names.parallelStream().forEachOrdered(System.out::println);3. reduce - 归约
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 求和
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
// 或
int sum2 = numbers.stream().reduce(0, Integer::sum);
// 15
// 求积
int product = numbers.stream()
.reduce(1, (a, b) -> a * b);
// 120
// 找最大值
Optional<Integer> max = numbers.stream()
.reduce((a, b) -> a > b ? a : b);
// 或
Optional<Integer> max2 = numbers.stream()
.reduce(Integer::max);4. count、min、max
java
// 计数
long count = names.stream().count();
// 最小值
Optional<Integer> min = numbers.stream().min(Integer::compare);
// 最大值
Optional<Integer> max = numbers.stream().max(Integer::compare);5. anyMatch、allMatch、noneMatch
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 是否有偶数
boolean hasEven = numbers.stream()
.anyMatch(n -> n % 2 == 0); // true
// 是否全是偶数
boolean allEven = numbers.stream()
.allMatch(n -> n % 2 == 0); // false
// 是否没有负数
boolean noNegative = numbers.stream()
.noneMatch(n -> n < 0); // true6. findFirst、findAny
java
// 找第一个
Optional<Integer> first = numbers.stream()
.filter(n -> n > 3)
.findFirst(); // Optional[4]
// 找任意一个(并行流中更高效)
Optional<Integer> any = numbers.parallelStream()
.filter(n -> n > 3)
.findAny();📖 五、实战应用
1. 复杂过滤和转换
java
List<Person> people = Arrays.asList(
new Person("Alice", 25, "Beijing"),
new Person("Bob", 30, "Shanghai"),
new Person("Charlie", 35, "Beijing"),
new Person("David", 28, "Shanghai")
);
// 找出北京30岁以下的人,返回姓名列表
List<String> names = people.stream()
.filter(p -> p.getCity().equals("Beijing"))
.filter(p -> p.getAge() < 30)
.map(Person::getName)
.collect(Collectors.toList());
// [Alice]2. 统计信息
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = numbers.stream()
.mapToInt(Integer::intValue)
.summaryStatistics();
System.out.println("Count: " + stats.getCount()); // 10
System.out.println("Sum: " + stats.getSum()); // 55
System.out.println("Min: " + stats.getMin()); // 1
System.out.println("Max: " + stats.getMax()); // 10
System.out.println("Average: " + stats.getAverage()); // 5.53. 分组和聚合
java
// 按城市分组,计算平均年龄
Map<String, Double> avgAgeByCity = people.stream()
.collect(Collectors.groupingBy(
Person::getCity,
Collectors.averagingInt(Person::getAge)
));
// {Beijing=30.0, Shanghai=29.0}
// 按城市分组,统计人数
Map<String, Long> countByCity = people.stream()
.collect(Collectors.groupingBy(
Person::getCity,
Collectors.counting()
));📖 六、并行流
1. 创建并行流
java
// 方式1:从顺序流转换
Stream<Integer> parallelStream = numbers.stream().parallel();
// 方式2:直接创建
Stream<Integer> parallelStream2 = numbers.parallelStream();2. 性能对比
java
List<Integer> numbers = IntStream.rangeClosed(1, 10_000_000)
.boxed()
.collect(Collectors.toList());
// 顺序流
long start = System.currentTimeMillis();
long sum = numbers.stream()
.mapToLong(Integer::longValue)
.sum();
long end = System.currentTimeMillis();
System.out.println("Sequential: " + (end - start) + " ms");
// 并行流
start = System.currentTimeMillis();
sum = numbers.parallelStream()
.mapToLong(Integer::longValue)
.sum();
end = System.currentTimeMillis();
System.out.println("Parallel: " + (end - start) + " ms");3. 注意事项
⚠️ 并行流不一定更快:
- 数据量小时,并行开销大于收益
- 操作简单时,并行开销大于收益
- 有状态操作(sorted、distinct)不适合并行
⚠️ 线程安全:
java
List<Integer> list = new ArrayList<>();
// ❌ 线程不安全
numbers.parallelStream()
.forEach(n -> list.add(n)); // 可能出错
// ✅ 使用线程安全的 collect
List<Integer> result = numbers.parallelStream()
.collect(Collectors.toList());⚠️ 常见陷阱
陷阱1:忘记 Stream 只能消费一次
Stream 是一次性流水线,执行终止操作后不能再次使用。需要复用数据时,保留集合或重新创建 Stream。
陷阱2:在中间操作里写副作用
map、filter、peek 应尽量保持无副作用。修改外部集合、累加外部变量会让代码难以理解,并行流下更危险。
陷阱3:滥用并行流
并行流不等于自动更快。数据量小、操作轻、涉及 IO、共享可变状态时,往往更慢或更容易出错。
陷阱4:用 Stream 牺牲可读性
多层嵌套、复杂分支、异常处理很重的逻辑,不一定适合写成 Stream。清晰的 for 循环仍然是好代码。
陷阱5:忽略装箱开销
大量数字处理应优先使用 IntStream、LongStream、DoubleStream,避免 Stream<Integer> 带来的装箱拆箱成本。
🆚 Java vs C 对比
| 特性 | C | Java Stream |
|---|---|---|
| 数据遍历 | for/while 手写循环 | 声明式流水线 |
| 函数传递 | 函数指针 | Lambda / 方法引用 |
| 惰性求值 | 手动控制 | 中间操作默认惰性 |
| 并行处理 | pthread/OpenMP 等 | parallelStream / ForkJoinPool |
对 C 程序员来说,Stream 像一条可组合的数据处理管线。它不是新集合,也不是线程模型,而是把“遍历、过滤、映射、聚合”抽象成统一的流水线。
💡 最佳实践
1. 链式调用保持简洁
java
// ✅ 清晰
people.stream()
.filter(p -> p.getAge() > 18)
.map(Person::getName)
.sorted()
.collect(Collectors.toList());
// ❌ 过长
people.stream().filter(p -> p.getAge() > 18).map(Person::getName).sorted().limit(10).distinct().collect(Collectors.toList());2. 优先使用方法引用
java
// ❌
names.stream().map(name -> name.toUpperCase())
// ✅
names.stream().map(String::toUpperCase)3. 避免副作用
java
// ❌ 有副作用
List<String> result = new ArrayList<>();
names.stream().forEach(name -> result.add(name.toUpperCase()));
// ✅ 无副作用
List<String> result = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());📝 练习
完成 练习/Ex33_Stream.java:
- Stream 创建和基本操作
- 中间操作练习
- 终止操作练习
- 复杂数据处理
- 并行流性能测试
- 综合:数据分析系统
🎓 下一步
- 第34课:Optional - 避免 NPE、链式调用、最佳实践