封面图上流动的「Stream」字样,正是 Java 8 以来最革命性的特性之一!你是否还在写冗长的 for 循环遍历集合?是否为过滤、排序、聚合数据写一堆重复代码?Stream 流的出现,以声明式编程风格将复杂的集合操作浓缩为一行代码,不仅可读性更强,还能轻松实现并行处理。本文将从 Stream 的核心概念(中间操作 / 终止操作)讲起,结合 10 + 实战案例(过滤去重、分组统计、链式调用),带你吃透 Stream 流的精髓,让代码更优雅、更高效!
Stream API 万物皆可一行代码
Stream专门针对集合的各种操作提供各种非常便利,简单,高效的API,
Stream API主要是通过Lambda表达式完成,极大的提高了程序的效率和可读性,
同时Stram API中自带的并行流使得并发处理集合的门槛再次降低,使用Stream API编程无需多写
怎样使用Stream流?
1. 获取流--> 2. 对流进行操作-->3.结束对流的操作
获取流的方式
集合类可通过
Collection.stream()
或Collection.parallelStream()
获取流;数组可通过
Arrays.stream(T[] array)
或Stream.of()
转换。IO 相关流如
Files.walk()
或BufferedReader.lines()
也可生成流。
中间操作(Intermediate Operations)
中间操作返回新流,支持链式调用,操作延迟执行(lazy)。
映射操作
map
:元素一对一转换。List<String> names = students.stream() .map(Student::getName) .collect(Collectors.toList());
flatMap
:扁平化多维结构。List<String> words = lines.stream() .flatMap(line -> Arrays.stream(line.split(" "))) .collect(Collectors.toList());
过滤与去重
filter
:保留符合谓词的元素。List<Student> adults = students.stream() .filter(s -> s.getAge() > 18) .collect(Collectors.toList());
distinct
:去除重复元素(依赖equals
方法)。
排序与裁剪
-
sorted
:自然排序或自定义比较器。
在Java流(Stream) API中,sorted
方法用于对流中的元素进行排序操作。它支持两种重载形式,可以根据需要选择无参数或传入自定义比较器(Comparator
)。下面我将逐步解释其用法,并提供真实可靠的示例代码,帮助您理解如何高效实现排序。
1. 无参数sorted方法
- 当调用
sorted()
方法而不传递任何参数时,要求流中的元素必须实现Comparable<T>
接口。这适用于元素类已经定义了自然排序顺序的情况。 - 示例:如果元素类(如
String
或自定义类)实现了Comparable
,则可以直接使用。List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.stream() .sorted() // 依赖String的Comparable实现 .forEach(System.out::println);
Alice
,Bob
,Charlie
。
2. 带Comparator参数的sorted方法
-
方法签名:
Stream<T> sorted(Comparator<? super T> comparator)
-
这允许您传入一个自定义比较器来定义排序规则,特别适用于元素类未实现
Comparable
或需要特定排序逻辑的场景。 -
使用lambda表达式可以简洁地定义比较器。
- 示例:基于对象的属性(如年龄)排序。
List<Student> students = Arrays.asList( new Student("Alice", 20), new Student("Bob", 18), new Student("Charlie", 22) ); // 使用lambda表达式排序:按年龄从小到大 students.stream() .sorted((s1, s2) -> Integer.compare(s1.getAge(), s2.getAge())) .forEach(System.out::println);
Bob(18)
,Alice(20)
,Charlie(22)
)。
- 示例:基于对象的属性(如年龄)排序。
-
更推荐使用
Comparator
的工厂方法(如Comparator.comparingInt
),以提高代码可读性和健壮性。- 示例:使用
Comparator.comparingInt
优化排序。students.stream() .sorted(Comparator.comparingInt(Student::getAge)) // 等价于lambda,但更简洁 .forEach(System.out::println);
- 示例:使用
3. 注意事项和最佳实践
- 性能考虑:
sorted
方法是一个有状态操作,可能影响流性能。对于大数据集,建议在排序前使用parallelStream()
(如果线程安全)。 - 降序排序:可以通过
Comparator.reversed()
实现降序。students.stream() .sorted(Comparator.comparingInt(Student::getAge).reversed()) .forEach(System.out::println);
Charlie(22)
,Alice(20)
,Bob(18)
)。 - 多字段排序:使用
Comparator.thenComparing
实现复合排序。students.stream() .sorted(Comparator.comparing(Student::getName) .thenComparingInt(Student::getAge)) .forEach(System.out::println);
limit
/skip
:截取前 N 项或跳过前 N 项。
调试辅助
peek
:遍历元素但不终结流,常用于调试。students.stream() .peek(s -> System.out.println("Processing: " + s.getName())) .collect(Collectors.toList());
终结操作(Terminal Operations)
终结操作触发流处理并返回结果或副作用。
遍历与聚合
forEach
:遍历元素。students.stream().forEach(System.out::println);
count
:统计元素数量。long count = students.stream().count();
min
/max
:需传入比较器。Optional<Student> oldest = students.stream() .max(Comparator.comparingInt(Student::getAge));
匹配与查找
anyMatch
/allMatch
/noneMatch
:检查元素是否匹配条件。boolean hasAdult = students.stream().anyMatch(s -> s.getAge() > 18);
findFirst
/findAny
:返回首个或任意元素(并行流中效率更高)。
归约与收集
2. 使用 Collectors
简化操作
Collectors
类提供了许多内置转换器,如 toList()
、toSet()
等,可以直接传入 collect
方法。例如:
3. groupingBy
分组操作
groupingBy
类似于 SQL 中的 GROUP BY,用于按指定条件分组元素。它有多个参数版本:
4. toMap
转换操作
toMap
用于将流转换为 Map
,需指定键和值的映射函数。例如:
// 将学生列表转换为 Map<年龄, 学生对象>
Map<Integer, Student> map = students.stream()
.collect(Collectors.toMap(Student::getAge, s -> s));
5. 基本数据类型流的限制
对于基本数据类型流(如 IntStream
、LongStream
、DoubleStream
),没有 collect
方法。这是因为基本类型涉及装箱(boxing)和拆箱(unboxing)操作,Java SDK 未将其集成到流收集机制中。替代方案是:
reduce
:聚合元素。Optional<Integer> totalAge = students.stream() .map(Student::getAge) .reduce(Integer::sum);
-
collect
:转换为集合或分组。 -
collect
是 Java Stream API 中的一个终端操作,用于将流中的元素收集到一个容器中(如集合或映射)。下面我将基于您提供的信息,逐步解释collect
的使用方法、常见场景和注意事项,确保内容真实可靠(基于 Java 8 及以上版本的规范)。1.
collect
方法的基本签名collect
方法有两个主要重载形式: - 第一个重载:接受三个参数(supplier、accumulator 和 combiner),用于自定义收集逻辑。
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
supplier
:提供一个新容器(如ArrayList::new
)。accumulator
:将元素添加到容器中(如List::add
)。combiner
:在并行流中合并多个容器(如List::addAll
)。
- 第二个重载:接受一个
Collector
对象,简化常见操作。<R, A> R collect(Collector<? super T, A, R> collector);
Collectors
工具类中的静态方法作为参数,因为它是更简洁的方式。 - 转换为列表:将流元素收集到
List
中。List<Integer> list = integers.stream().collect(Collectors.toList());
ArrayList
。 - 转换为集合:将流元素收集到
Set
中。Set<Integer> set = integers.stream().collect(Collectors.toSet());
HashSet
。 - 单参数版本:按分类函数分组,返回
Map
。// 按学生年龄分组,返回 Map<Integer, List<Student>> Map<Integer, List<Student>> map = students.stream() .collect(Collectors.groupingBy(Student::getAge));
HashMap
作为容器。 - 多参数版本:支持自定义容器和下游收集器。
- 两个参数:指定分类函数和下游收集器(如
toList()
)。 - 三个参数:添加容器类型(如
TreeMap::new
)。 例如,您提到的例子:按年龄分组,但只收集学生姓名而非整个对象。
// 按年龄分组,每组存储学生姓名列表 Map<Integer, List<String>> map = students.stream() .collect(Collectors.groupingBy( Student::getAge, Collectors.mapping(Student::getName, Collectors.toList()) ));
- 第一个参数
Student::getAge
是分类依据。 - 第二个参数
Collectors.mapping(...)
是下游收集器,其中mapping
将每个学生映射到姓名,再用toList()
收集为列表。 如果省略容器参数,默认仍为HashMap
。
- 两个参数:指定分类函数和下游收集器(如
- 第一个参数
Student::getAge
定义键(key)。 - 第二个参数
s -> s
定义值(value),这里直接使用学生对象。 注意事项: - 如果键重复,会抛出
IllegalStateException
。解决方法是添加第三个参数(合并函数),如(oldValue, newValue) -> oldValue
保留第一个值。 - 默认使用
HashMap
,但可以通过第四个参数指定其他Map
实现(如TreeMap::new
)。 - 使用
toArray()
方法转换为数组。int[] array = intStream.toArray();
- 或者,先将流转换为对象流(如
boxed()
),再使用collect
。List<Integer> list = intStream.boxed().collect(Collectors.toList());
- 特殊转换
toArray
:转为数组,可指定类型。Student[] array = students.stream().toArray(Student[]::new);
并行流注意事项
通过 parallelStream()
可启用并行处理,但需确保操作无状态且避免共享变量。以下场景适合并行流:
- 数据量较大且处理耗时。
- 任务可独立分片(如过滤、映射)。
List<Student> adults = students.parallelStream()
.filter(s -> s.getAge() > 18)
.collect(Collectors.toList());