Java stream流介绍
Java 8 引入了 Stream API,这是一个非常强大的工具,专门用于简化对集合(如 List
、Set
、Map
)及数组等数据源的操作。它为我们提供了一种更加声明式和函数式的编程风格,允许我们使用流畅的链式操作对数据进行处理。Stream API 主要被用于对集合中的元素进行批量处理,包括过滤、转换、排序和聚合等操作。
什么是 Stream?
Stream 是元素的序列,类似于集合,但与集合不同的是,Stream 不存储数据,它更多的是描述数据的传输和转换。它类似于 SQL 中的查询操作,提供了一组对数据进行操作的函数式工具方法,能够更简洁、更高效地处理集合中的数据。
Stream 是单向的、不可变的,并且只会被操作一次,操作后流就会被消费掉。它的核心特点包括:
- 惰性求值:Stream 中的中间操作(如
filter
、map
等)不会立即执行,只有终端操作(如collect
、forEach
)执行时,Stream 才会进行真正的计算。 - 声明式编程:不需要关心数据的具体操作细节,只需要关注要做的操作。
- 并行处理:可以通过
parallelStream()
轻松实现并行数据处理。
Stream 的两类操作
Stream 中的操作主要分为两大类:中间操作 和 终端操作。
1. 中间操作
中间操作会返回一个新的流,但它们不会触发流的执行,只有当终端操作执行时,整个流的处理才会开始。常见的中间操作有:
- filter(Predicate):根据条件过滤数据,返回符合条件的元素。
- map(Function):将元素转换为其他类型,通常用于对象属性的提取或数据格式的转换。
- flatMap(Function):将每个元素映射为一个流,并将这些流扁平化为一个单一流。
- sorted():对元素进行排序,支持自然排序和自定义排序。
- distinct():去除流中重复的元素。
- limit(n) 和 skip(n):截取前 n 个元素,或跳过前 n 个元素。
示例代码:
java
复制代码
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Alice");
List<String> distinctNames = names.stream()
.filter(name -> name.length() > 3) // 过滤长度大于3的名字
.distinct() // 去重
.sorted() // 排序
.collect(Collectors.toList()); // 收集结果到List
2. 终端操作
终端操作会触发流的执行,并且只执行一次。流被消费之后就无法再次使用。常见的终端操作有:
- forEach(Consumer):对流中的每个元素执行给定的动作。
- collect(Collector):将流中的元素收集到某种容器中,如
List
、Set
、Map
等。 - reduce(BinaryOperator):将流中的元素组合成一个值,通常用于聚合操作,如求和、求积。
- count():返回流中元素的个数。
- anyMatch(Predicate)、allMatch(Predicate)、noneMatch(Predicate):判断流中是否有任意、所有、或没有元素满足给定条件。
示例代码:
java
复制代码
int sum = IntStream.range(1, 10) // 创建从1到9的整数流
.reduce(0, (a, b) -> a + b); // 使用reduce求和
下面将给与一个稍微复杂一点的例子进行讲解
private List<String> updateAndInsertPerson(List<CjlhData> cjlhData,List<SysUser> accountInfos){
List<String> returnList = new ArrayList<>();
List<TalentInfoVo> talentInfoVoList = cjlhData.stream().map(CjlhData::getTalentInfo).filter(ObjectUtil::isNotNull).collect(Collectors.toList());
List<SysUser> personList = accountInfos.stream()
.filter(c -> "person".equals(c.getUserType()))
.collect(Collectors.toList());
Map<Long,String> personUserNameToIdMap = personList.stream().collect(Collectors.toMap( SysUser::getUserId,SysUser::getUserName));
//分出各部分是修改的还是插入的(个人)
if (CollectionUtils.isNotEmpty(personList)){
List<String> personUserNames = personList.stream().map(SysUser::getUserName).collect(Collectors.toList());
List<SysUser> personUsers = sysUserService.selectListByUserNames(personUserNames);
if (CollectionUtils.isNotEmpty(personUsers)){
Set<String> personUsersSet = personUsers.stream()
.map(SysUser::getUserName)
.collect(Collectors.toSet());
//分出personUserNames在personUsers中的和不在personUsers中的
Map<Boolean, List<String>> partitionedUserNames = personUserNames.stream()
.collect(Collectors.partitioningBy(personUsersSet::contains));
List<String> inPersonUsers = partitionedUserNames.get(true);
List<String> notInPersonUsers = partitionedUserNames.get(false);
if (CollectionUtils.isNotEmpty(inPersonUsers)){
//修改的数据 将id附上 如果inPersonUsers在personUsers中的id赋值给inPersonUsers
Map<String, Long> userNameToIdMap = personUsers.stream()
.collect(Collectors.toMap(SysUser::getUserName, SysUser::getUserId));
List<SysUser> updatedPersonList = personList.stream()
.filter(u -> inPersonUsers.contains(u.getUserName()))
.peek(u -> u.setUserId(userNameToIdMap.get(u.getUserName())))
.collect(Collectors.toList());
log.info("分离sys_user个人信息更新信息结束");
sysUserService.insertOrUpdateUserBatch(updatedPersonList);
//查询出所有更新后的userId是否有talentId
List<Long> updatedUserIds = updatedPersonList.stream().map(SysUser::getUserId).collect(Collectors.toList());
List<TalentInfoVo> talentInfos = talentInfoService.queryByUserIds(updatedUserIds);
Map<Long, Long> talentUserIdAndTalentIdMap = talentInfos.stream().collect(Collectors.toMap(TalentInfoVo::getUserId, TalentInfoVo::getTalentId));
log.info("批量更新sys_user个人信息结束");
//对talentInfoVoList中与personList的userId相同则将updatedPersonList中返回的userId赋予给匹配的talentInfoVoList的userId,updatedPersonList与personList匹配条件是userName
Map<String , Long> updatedUserNameToIdMap = updatedPersonList.stream()
.collect(Collectors.toMap(SysUser::getUserName, SysUser::getUserId));
talentInfoVoList.stream().forEach(t -> {
if (personUserNameToIdMap.get(t.getUserId()) != null){
if (updatedUserNameToIdMap.get(personUserNameToIdMap.get(t.getUserId())) != null){
t.setUserId(updatedUserNameToIdMap.get(personUserNameToIdMap.get(t.getUserId())));
if (ObjectUtil.isNotEmpty(talentUserIdAndTalentIdMap)){
t.setTalentId(talentUserIdAndTalentIdMap.get(t.getUserId()));
}
}
}
});
}
if (CollectionUtils.isNotEmpty(notInPersonUsers)){
//插入的数据
List<SysUser> notInPersonUsersList = personList.stream()
.filter(u -> notInPersonUsers.contains(u.getUserName()))
.peek(u -> u.setUserId(null))
.collect(Collectors.toList());
log.info("分离sys_user个人信息新增信息结束");
sysUserService.insertOrUpdateUserBatch(notInPersonUsersList);
log.info("插入sys_user个人信息结束");
Map<String, Long> insertUserNameToIdMap = notInPersonUsersList.stream().collect(Collectors.toMap(SysUser::getUserName, SysUser::getUserId));
talentInfoVoList.stream().forEach(t -> {
if (personUserNameToIdMap.get(t.getUserId()) != null){
if (insertUserNameToIdMap.get(personUserNameToIdMap.get(t.getUserId())) != null){
t.setUserId(insertUserNameToIdMap.get(personUserNameToIdMap.get(t.getUserId())));
}
}
});
}
if (CollectionUtils.isNotEmpty(talentInfoVoList)){
log.info("批量插入和更新个人信息开始");
List<TalentInfo> talentInfos = BeanUtil.copyToList(talentInfoVoList, TalentInfo.class);
talentInfoService.insertOrUpdateBatch(talentInfos);
List<String> tPerosn = talentInfos.stream().map(TalentInfo::getUserId).map(String::valueOf).collect(Collectors.toList());
returnList.addAll(tPerosn);
log.info("批量插入和更新个人信息结束");
}
} else {
//否则就是全部插入的
if (CollectionUtils.isNotEmpty(personList)){
List<SysUser> insert = personList.stream().peek(u -> u.setUserId(null)).collect(Collectors.toList());
sysUserService.insertOrUpdateUserBatch(insert);
Map<String, Long> insertUserNameToIdMap = insert.stream().collect(Collectors.toMap(SysUser::getUserName, SysUser::getUserId));
talentInfoVoList.stream().forEach(t -> {
if (personUserNameToIdMap.get(t.getUserId()) != null){
if (insertUserNameToIdMap.get(personUserNameToIdMap.get(t.getUserId())) != null){
t.setUserId(insertUserNameToIdMap.get(personUserNameToIdMap.get(t.getUserId())));
}
}
});
if (CollectionUtils.isNotEmpty(talentInfoVoList)){
List<TalentInfo> talentInfos = BeanUtil.copyToList(talentInfoVoList, TalentInfo.class);
talentInfoService.insertOrUpdateBatch(talentInfos);
List<String> tPerson = talentInfos.stream().map(TalentInfo::getUserId).map(String::valueOf).collect(Collectors.toList());
returnList.addAll(tPerson);
}
}
}
}
return returnList;
}
在这段代码中,Stream API
被大量运用于数据的过滤、转换和处理。它帮助将传统的迭代过程简化为声明式编程,提高代码的可读性与简洁性。以下是针对代码中使用 stream
流的详细解析:
1. 过滤与映射操作
java
复制代码
List<TalentInfoVo> talentInfoVoList = cjlhData.stream()
.map(CjlhData::getTalentInfo)
.filter(ObjectUtil::isNotNull)
.collect(Collectors.toList());
- stream(): 将
cjlhData
列表转化为流。 - map(CjlhData::getTalentInfo): 通过
map
方法,将每个CjlhData
对象中的TalentInfo
提取出来。map
是一种转换操作,它将一个元素转化为另一个元素。 - filter(ObjectUtil::isNotNull): 过滤掉
TalentInfo
为null
的元素,保留非空数据。 - collect(Collectors.toList()): 将流中的元素收集回到一个
List
列表中。collect
是终端操作,将流转化为我们需要的集合类型。
这个流程非常直观,替代了传统的嵌套循环与手动判断,使用流可以轻松实现批量数据提取与过滤。
2. 根据条件过滤用户类型
java
复制代码
List<SysUser> personList = accountInfos.stream()
.filter(c -> "person".equals(c.getUserType()))
.collect(Collectors.toList());
- filter(c -> “person”.equals(c.getUserType())): 使用
filter
过滤出用户类型为"person"
的用户。这是一种典型的条件过滤操作。
这段代码利用 stream
的 filter
方法,比传统的 for
循环+if
判断更加简洁、清晰。
3. 将 List 转换为 Map
java
复制代码
Map<Long, String> personUserNameToIdMap = personList.stream()
.collect(Collectors.toMap(SysUser::getUserId, SysUser::getUserName));
- collect(Collectors.toMap(SysUser::getUserId, SysUser::getUserName)): 这里使用
toMap
方法将personList
转化为Map
,键是SysUser
的userId
,值是userName
。这种转换通常用于快速查找某些对象的键值对信息。
4. 分组和分区
java
复制代码
Map<Boolean, List<String>> partitionedUserNames = personUserNames.stream()
.collect(Collectors.partitioningBy(personUsersSet::contains));
- partitioningBy: 这是一个特殊的
Collector
,它根据某个条件将元素分为两部分。这里根据personUsersSet
是否包含某个userName
,将personUserNames
分为存在和不存在两组。这个操作在传统编程中需要大量的if
判断,而Stream
API 让它更加直观。
5. 批量更新与插入
java
复制代码
List<SysUser> updatedPersonList = personList.stream()
.filter(u -> inPersonUsers.contains(u.getUserName()))
.peek(u -> u.setUserId(userNameToIdMap.get(u.getUserName())))
.collect(Collectors.toList());
- peek(u -> u.setUserId(userNameToIdMap.get(u.getUserName())):
peek
是一种中间操作,允许我们在流中处理每个元素而不影响主流程。在这里为符合条件的用户设置userId
。 - filter: 用于筛选符合更新条件的用户。
- collect(Collectors.toList()): 最终将流中的元素收集成列表。
Stream 与并行处理
Java Stream API 还支持 并行流,通过 parallelStream()
可以轻松启用多线程处理数据。当集合数据量较大时,启用并行流可以提升性能,它会将任务拆分给多个线程去处理,然后合并结果。
示例:
java
复制代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = numbers.parallelStream() // 使用并行流
.reduce(0, Integer::sum); // 求和
注意:并行流并不总是能提高性能,尤其在数据量较小时,启用多线程可能反而带来不必要的开销。适合并行处理的任务通常是计算密集型或处理大量数据的任务。
Stream 的优势
- 简洁性:通过链式调用操作,大大减少了样板代码的编写,提高了代码的可读性。
- 函数式编程:Stream API 结合了 Lambda 表达式,带来了更加函数式的编程风格,使代码更加简洁和优雅。
- 惰性求值与短路操作:Stream 的惰性求值机制避免了不必要的计算,中间操作不会立即执行,直到需要结果时才开始处理。
- 并行处理:简单地将
stream()
替换为parallelStream()
,就可以让程序自动利用多核 CPU 提高处理性能。
典型的 Stream 使用场景
- 大数据处理:流式处理数据,提高性能。
- 集合操作:如过滤、转换、排序、合并、去重等。
- 批量操作:对一组数据执行批量更新、转换等操作。
- 多线程计算:在需要处理大量数据时,可以使用并行流提高计算效率。
小结
Java Stream API 提供了一种简洁、声明式的方式来操作集合数据。它不仅提升了代码的可读性,还提高了数据处理的效率。通过支持并行流,Stream API 能更好地适应现代多核 CPU 的处理需求,使 Java 开发者能够更加高效地编写程序。
Stream API 的引入,使得 Java 语言更好地支持了函数式编程范式,在现代 Java 开发中已经成为处理数据的主流方式。