Skip to content

第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 特点

  1. 不是数据结构 - 不存储数据,只是对数据源的视图
  2. 惰性求值 - 中间操作不会立即执行
  3. 一次性 - 每个 Stream 只能使用一次
  4. 支持链式调用 - 流式风格
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, B

3. 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, ..., 18

5. 其他方式

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);  // true

6. 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.5

3. 分组和聚合

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:在中间操作里写副作用

mapfilterpeek 应尽量保持无副作用。修改外部集合、累加外部变量会让代码难以理解,并行流下更危险。

陷阱3:滥用并行流

并行流不等于自动更快。数据量小、操作轻、涉及 IO、共享可变状态时,往往更慢或更容易出错。

陷阱4:用 Stream 牺牲可读性

多层嵌套、复杂分支、异常处理很重的逻辑,不一定适合写成 Stream。清晰的 for 循环仍然是好代码。

陷阱5:忽略装箱开销

大量数字处理应优先使用 IntStreamLongStreamDoubleStream,避免 Stream&lt;Integer&gt; 带来的装箱拆箱成本。


🆚 Java vs C 对比

特性CJava 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

  1. Stream 创建和基本操作
  2. 中间操作练习
  3. 终止操作练习
  4. 复杂数据处理
  5. 并行流性能测试
  6. 综合:数据分析系统

🎓 下一步

  • 第34课:Optional - 避免 NPE、链式调用、最佳实践