Appearance
Stream API
Stream 简介
Stream 是 Java 8 引入的数据处理API,用于函数式操作集合。
┌─────────────────────────────────────────────────────────────────┐
│ Stream 操作流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 数据源 ──→ 中间操作 ──→ 终端操作 │
│ │
│ Collection filter() forEach() │
│ Arrays map() collect() │
│ I/O distinct() reduce() │
│ sorted() findFirst() │
│ ... count() │
│ │
└─────────────────────────────────────────────────────────────────┘Stream vs Collection
| 特性 | Collection | Stream |
|---|---|---|
| 存储 | 主动存储数据 | 描述数据处理过程 |
| 遍历 | 可多次遍历 | 只能遍历一次 |
| 延迟执行 | 不支持 | 支持(中间操作是惰性的) |
| 并行 | 手动实现 | .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 | 查找 |
⚠️ 易错点提醒:
- Stream 只能遍历一次,重复遍历会抛出异常
- 并行 Stream 不保证顺序
- 链式操作是惰性执行的,只有终端操作才触发执行
- filter 后再 map vs 先 map 再 filter 效率可能不同