Skip to content

Stream API

Stream 简介

Stream 是 Java 8 引入的数据处理API,用于函数式操作集合。

┌─────────────────────────────────────────────────────────────────┐
│                       Stream 操作流程                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   数据源  ──→  中间操作  ──→  终端操作                         │
│                                                                  │
│   Collection    filter()         forEach()                      │
│   Arrays       map()            collect()                       │
│   I/O          distinct()       reduce()                        │
│                sorted()         findFirst()                     │
│                ...              count()                          │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Stream vs Collection

特性CollectionStream
存储主动存储数据描述数据处理过程
遍历可多次遍历只能遍历一次
延迟执行不支持支持(中间操作是惰性的)
并行手动实现.parallelStream()
用途存储数据处理数据

创建 Stream

java
// 1. 从集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();

// 2. 从数组创建
String[] arr = {"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(arr);

// 3. Stream.of()
Stream<String> stream3 = Stream.of("a", "b", "c");

// 4. 无限 Stream
Stream<Integer> stream4 = Stream.iterate(0, n -> n + 2).limit(10);
Stream<Double> stream5 = Stream.generate(Math::random).limit(5);

// 5. 并行 Stream
Stream<String> parallelStream = list.parallelStream();

中间操作

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]

// 筛选长度大于3的字符串
List<String> names = Arrays.asList("Tom", "Jerry", "Mike", "Jo");
List<String> longNames = names.stream()
    .filter(name -> name.length() > 3)
    .collect(Collectors.toList());
// 结果: ["Jerry", "Mike"]

map - 转换

java
List<String> words = Arrays.asList("hello", "world");

// 转换为大写
List<String> upper = words.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());
// 结果: ["HELLO", "WORLD"]

// 提取长度
List<Integer> lengths = words.stream()
    .map(String::length)
    .collect(Collectors.toList());
// 结果: [5, 5]

// flatMap - 扁平化
List<String> sentences = Arrays.asList("hello world", "java stream");
List<String> flatWords = sentences.stream()
    .flatMap(s -> Arrays.stream(s.split(" ")))
    .collect(Collectors.toList());
// 结果: ["hello", "world", "java", "stream"]

distinct - 去重

java
List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 1, 4, 3);
List<Integer> distinct = numbers.stream()
    .distinct()
    .collect(Collectors.toList());
// 结果: [1, 2, 3, 4]

sorted - 排序

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

// 自然排序
List<Integer> sorted = numbers.stream()
    .sorted()
    .collect(Collectors.toList());
// 结果: [1, 1, 2, 3, 4, 5, 6, 9]

// 倒序
List<Integer> reversed = numbers.stream()
    .sorted(Comparator.reverseOrder())
    .collect(Collectors.toList());

// 自定义排序
List<String> names = Arrays.asList("Tom", "Jerry", "Mike");
List<String> byLength = names.stream()
    .sorted(Comparator.comparingInt(String::length).reversed())
    .collect(Collectors.toList());
// 结果: ["Jerry", "Mike", "Tom"]

limit 与 skip

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

// 取前3个
List<Integer> limited = numbers.stream()
    .limit(3)
    .collect(Collectors.toList());
// 结果: [1, 2, 3]

// 跳过前2个
List<Integer> skipped = numbers.stream()
    .skip(2)
    .collect(Collectors.toList());
// 结果: [3, 4, 5]

// 分页:跳过前2个,取2个
List<Integer> page = numbers.stream()
    .skip(2)
    .limit(2)
    .collect(Collectors.toList());
// 结果: [3, 4]

终端操作

collect - 收集

java
List<String> names = Arrays.asList("Tom", "Jerry", "Mike");

// 收集为 List
List<String> list = names.stream()
    .filter(n -> n.length() > 3)
    .collect(Collectors.toList());

// 收集为 Set
Set<String> set = names.stream()
    .filter(n -> n.length() > 3)
    .collect(Collectors.toSet());

// 收集为 Map
Map<String, Integer> map = names.stream()
    .collect(Collectors.toMap(name -> name, String::length));

// 收集为特定集合
TreeSet<String> treeSet = names.stream()
    .collect(Collectors.toCollection(TreeSet::new));

// 统计
IntSummaryStatistics stats = names.stream()
    .collect(Collectors.summarizingInt(String::length));
System.out.println(stats.getAverage());  // 平均长度
System.out.println(stats.getMax());    // 最大长度

forEach - 遍历

java
List<String> names = Arrays.asList("Tom", "Jerry", "Mike");

// 普通遍历
names.stream().forEach(System.out::println);

// 并行遍历(无序)
names.parallelStream().forEach(System.out::println);

// 按顺序并行遍历
names.parallelStream().forEachOrdered(System.out::println);

reduce - 聚合

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

// 求和(方式1)
int sum1 = numbers.stream().reduce(0, Integer::sum);

// 求和(方式2)
OptionalInt sum2 = numbers.stream().mapToInt(Integer::intValue).reduce(Integer::sum);

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

// 字符串拼接
List<String> words = Arrays.asList("Hello", "World");
String sentence = words.stream().reduce("", (a, b) -> a + " " + b);
// 结果: " Hello World"

find - 查找

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

// 找到第一个
Optional<Integer> first = numbers.stream().findFirst();

// 找到任意一个
Optional<Integer> any = numbers.parallelStream().findAny();

// 找到第一个满足条件的
Optional<Integer> found = numbers.stream()
    .filter(n -> n > 3)
    .findFirst();

match - 匹配

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

// 是否有偶数
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);

// 是否全是正数
boolean allPositive = numbers.stream().allMatch(n -> n > 0);

// 是否没有负数
boolean noneNegative = numbers.stream().noneMatch(n -> n < 0);

count - 计数

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

long count = numbers.stream().count();

long evenCount = numbers.stream()
    .filter(n -> n % 2 == 0)
    .count();

实战示例

统计操作

java
class Student {
    private String name;
    private int age;
    private int score;

    // getters and setters
}

List<Student> students = Arrays.asList(
    new Student("Tom", 18, 85),
    new Student("Jerry", 19, 92),
    new Student("Mike", 18, 78),
    new Student("John", 20, 90)
);

// 统计年龄分布
Map<Integer, Long> ageCount = students.stream()
    .collect(Collectors.groupingBy(Student::getAge, Collectors.counting()));

// 统计每个年龄的平均分
Map<Integer, Double> avgScore = students.stream()
    .collect(Collectors.groupingBy(
        Student::getAge,
        Collectors.averagingInt(Student::getScore)
    ));

// 找出最高分学生
Optional<Student> topStudent = students.stream()
    .max(Comparator.comparingInt(Student::getScore));

// 按分数分组
Map<Integer, List<Student>> byScoreLevel = students.stream()
    .collect(Collectors.groupingBy(s -> {
        int score = s.getScore();
        if (score >= 90) return "A";
        if (score >= 80) return "B";
        if (score >= 70) return "C";
        return "D";
    }));

字符串处理

java
List<String> sentences = Arrays.asList(
    "Java is powerful",
    "Python is popular",
    "Java is enterprise"
);

// 单词统计
Map<String, Long> wordCount = Arrays.asList(sentences.stream()
    .flatMap(s -> Arrays.stream(s.split(" ")))
    .map(String::toLowerCase)
    .toArray(String[]::new))
    .stream()
    .collect(Collectors.groupingBy(w -> w, Collectors.counting()));

// 找出包含 "Java" 的句子
List<String> javaSentences = sentences.stream()
    .filter(s -> s.contains("Java"))
    .collect(Collectors.toList());

并行 Stream

java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 顺序执行
long start = System.currentTimeMillis();
long sum1 = numbers.stream()
    .mapToLong(Long::new)
    .sum();
System.out.println("顺序: " + (System.currentTimeMillis() - start) + "ms");

// 并行执行
start = System.currentTimeMillis();
long sum2 = numbers.parallelStream()
    .mapToLong(Long::new)
    .sum();
System.out.println("并行: " + (System.currentTimeMillis() - start) + "ms");

// 注意事项
numbers.parallelStream()
    .forEach(System.out::println);  // 无序输出

numbers.parallelStream()
    .forEachOrdered(System.out::println);  // 按顺序输出

性能注意事项

┌─────────────────────────────────────────────────────────────────┐
│                      Stream 性能建议                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 少量数据用顺序 Stream,大量数据用并行 Stream                  │
│                                                                  │
│  2. 避免在中间操作中修改数据源                                    │
│                                                                  │
│  3. 链式操作尽量在最后 collect(),避免多次遍历                     │
│                                                                  │
│  4. 并行 Stream 不适合的场景:                                   │
│     • 涉及顺序依赖的操作(如 limit、skip)                        │
│     • 有状态的操作(如 sorted、distinct)                         │
│     • 小数据量(并行开销大于收益)                                │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

总结

Stream 核心知识点

操作类型方法说明
中间操作filter, map, flatMap转换数据
中间操作distinct, sorted去重排序
中间操作limit, skip分页
终端操作collect收集结果
终端操作forEach遍历
终端操作reduce聚合
终端操作findFirst, anyMatch查找

⚠️ 易错点提醒

  1. Stream 只能遍历一次,重复遍历会抛出异常
  2. 并行 Stream 不保证顺序
  3. 链式操作是惰性执行的,只有终端操作才触发执行
  4. filter 后再 map vs 先 map 再 filter 效率可能不同