Java 8引入的Stream API彻底改变了集合数据处理的写法,用声明式的链式调用替代了以前的for循环。代码更简洁、可读性更好,还能方便地切换到并行流。这篇系统整理Stream的常用操作。
创建Stream
import java.util.*;
import java.util.stream.*;
// 从集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
// 从数组创建
int[] arr = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(arr);
// Stream.of
Stream<String> s = Stream.of("x", "y", "z");
// 生成无限流
Stream<Integer> randoms = Stream.generate(() -> new Random().nextInt(100));
Stream<Integer> naturals = Stream.iterate(0, n -> n + 1);
中间操作
中间操作返回新的Stream,可以链式调用,且是惰性求值(只有遇到终止操作才真正执行)。
filter
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
List<Integer> evens = nums.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// [2, 4, 6, 8]
map
List<String> names = Arrays.asList("alice", "bob", "charlie");
List<String> upperNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// [ALICE, BOB, CHARLIE]
// mapToInt / mapToLong / mapToDouble 避免装箱
int totalLength = names.stream()
.mapToInt(String::length)
.sum();
flatMap
把嵌套结构展平:
List<List<Integer>> nested = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5),
Arrays.asList(6, 7, 8, 9)
);
List<Integer> flat = nested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
sorted
List<String> sorted = names.stream()
.sorted() // 自然排序
.collect(Collectors.toList());
// 自定义排序
List<String> sortedByLength = names.stream()
.sorted(Comparator.comparingInt(String::length).reversed())
.collect(Collectors.toList());
// [charlie, alice, bob]
distinct / limit / skip / peek
// 去重
List<Integer> unique = Arrays.asList(1, 2, 2, 3, 3, 3).stream()
.distinct()
.collect(Collectors.toList()); // [1, 2, 3]
// 取前N个
List<Integer> firstThree = nums.stream()
.limit(3)
.collect(Collectors.toList()); // [1, 2, 3]
// 跳过前N个
List<Integer> afterTwo = nums.stream()
.skip(2)
.collect(Collectors.toList()); // [3, 4, 5, 6, 7, 8]
// peek用于调试(不影响流)
nums.stream()
.filter(n -> n > 3)
.peek(n -> System.out.println("filtered: " + n))
.map(n -> n * 2)
.collect(Collectors.toList());
终止操作
collect
最常用的终止操作,配合Collectors工具类:
// 转List
List<String> list = stream.collect(Collectors.toList());
// 转Set
Set<String> set = stream.collect(Collectors.toSet());
// 转Map
Map<Integer, String> map = names.stream()
.collect(Collectors.toMap(String::length, s -> s, (a, b) -> a));
// joining
String joined = names.stream()
.collect(Collectors.joining(", ", "[", "]"));
// [alice, bob, charlie]
// 分组
Map<Integer, List<String>> grouped = names.stream()
.collect(Collectors.groupingBy(String::length));
// {3=[bob], 5=[alice], 7=[charlie]}
// 分区(按条件分成两组)
Map<Boolean, List<Integer>> partitioned = nums.stream()
.collect(Collectors.partitioningBy(n -> n > 5));
forEach
names.stream().forEach(System.out::println);
// 注意:forEach是终止操作,执行后Stream就消费完了
reduce
// 求和
Optional<Integer> sum = nums.stream()
.reduce(Integer::sum);
// 带初始值
int total = nums.stream()
.reduce(0, Integer::sum);
// 求最大值
Optional<Integer> max = nums.stream()
.reduce(Integer::max);
其他终止操作
long count = stream.count();
boolean anyMatch = stream.anyMatch(s -> s.startsWith("a"));
boolean allMatch = stream.allMatch(s -> s.length() > 2);
Optional<String> first = stream.findFirst();
Optional<String> any = stream.findAny(); // 并行流中有用
并行流
// 直接创建并行流
list.parallelStream()
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
// 普通流转并行流
list.stream()
.parallel()
.map(String::toUpperCase)
.collect(Collectors.toList());
并行流底层用ForkJoinPool,适合数据量大、无状态的操作。注意事项:
- 数据量小时并行可能更慢(线程调度开销)
- 不要在并行流中修改共享状态
- 有序性要求高的场景慎用
实际例子
统计一段文本中每个单词的出现次数:
String text = "hello world hello java world hello stream";
Map<String, Long> wordCount = Arrays.stream(text.split("\\s+"))
.collect(Collectors.groupingBy(w -> w, Collectors.counting()));
// {hello=3, world=2, java=1, stream=1}
从用户列表中找出活跃用户的邮箱:
List<String> activeEmails = users.stream()
.filter(User::isActive)
.filter(u -> u.getAge() >= 18)
.sorted(Comparator.comparing(User::getName))
.map(User::getEmail)
.distinct()
.collect(Collectors.toList());
小结
Stream API的核心模式就是:创建流 -> 中间操作(filter/map/sorted等)-> 终止操作(collect/forEach/neduce等)。习惯了之后,集合处理的代码量会少很多,而且表达力更强。建议多用,但不要为了用Stream而用——简单的循环就能搞定的,没必要硬凹成Stream写法。