前言
Java 8 引入了 Stream API,它是一种用于处理集合(Collection)数据的强大工具。Stream 不是数据结构,而是对数据源进行操作的一种方式,支持声明式、函数式的操作,如过滤、映射、排序等。
Stream 操作分为中间操作和终端操作:
- 中间操作(Intermediate operations):返回一个 Stream,可以链式调用。
- 终端操作(Terminal operations):触发实际计算,返回非 Stream 类型的结果。
获取 Stream 的方式
常见的创建 Stream 的方法如下:
// 从集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
// 从数组创建
int[] arr = {1, 2, 3};
IntStream intStream = Arrays.stream(arr);
// 使用 Stream.of 创建
Stream<String> stream = Stream.of("a", "b", "c");
// 无限流(generate / iterate)
Stream<Integer> infiniteStream = Stream.generate(() -> 1);
Stream<Integer> iterateStream = Stream.iterate(0, n -> n + 2);
Stream 的常用操作
filter
用于过滤符合条件的数据:
List<String> filtered = list.stream()
.filter(s -> s.startsWith("J"))
.toList();
map
将每个元素映射成另一个对象:
List<String> upperCase = list.stream()
.map(String::toUpperCase)
.toList();
sorted
对流中的元素进行排序:
List<String> sorted = list.stream()
.sorted()
.toList();
也可以自定义排序规则
.sorted((s1, s2) -> s2.compareTo(s1))
或者使用 Comparator:
.sorted(Comparator.reverseOrder())
limit / skip
限制流的大小或跳过前 N 个元素:
List<String> limited = list.stream()
.limit(2)
.toList();
List<String> skipped = list.stream()
.skip(2)
.toList();
distinct
去重,基于 equals() 方法判断重复项:
List<String> unique = list.stream()
.distinct()
.toList();
forEach
遍历流中的每个元素:
list.stream()
.forEach(System.out::println);
collect
收集流的结果到容器中,常配合 Collectors 使用:
List<String> filteredList = list.stream()
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
String joined = list.stream()
.collect(Collectors.joining(", "));
reduce
归约操作,合并流中的元素为一个结果:
Optional<String> reduced = list.stream()
.reduce((s1, s2) -> s1 + "-" + s2);
也可以提供初始值:
String result = list.stream()
.reduce("start", (acc, s) -> acc + "-" + s);
并行流(Parallel Stream)
使用并行流可以提高大数据量下的处理效率:
List<String> parallelResult = list.parallelStream()
.filter(s -> s.startsWith("A"))
.toList();
需要注意线程安全问题和是否适合并行处理。
注意事项
- Stream 只能被消费一次,再次使用会抛出异常。
- 中间操作是惰性求值的,只有遇到终端操作才会真正执行。
- 避免在流中修改外部变量,容易引发并发问题。
- 适当使用并行流,不是所有情况都适用。
- 注意空指针异常,尤其是在处理集合时。
总结
Stream API 极大地简化了对集合的操作,使得代码更简洁、可读性更高。通过链式调用,我们可以清晰地表达数据处理逻辑。但也要注意其性能特性和使用场景,合理选择是否使用 Stream 或传统循环。