探索 Java 8 Stream API:从排序到过滤的全面指南
探索 Java 8 Stream API:从排序到过滤的全面指南
dong4jJava 8 引进了强大的 Stream API,使得集合操作更加简洁优雅。本文将详细介绍如何使用 Java 8 的 Stream API 进行 List 转 Map、List 排序、列表过滤及 Map 中的条件操作。
使用 Java 8 Lambda 将 list 转为 map
常用方式
要将一个 List 转换为 Map,我们可以利用 Collectors.toMap()
方法。例如:
1 | public Map<Long, String> getIdNameMap(List<Account> accounts) { |
收集成实体本身 map
有时候我们可能希望将列表中的对象直接映射到 Map 中作为值,可以这样做:
1 | public Map<Long, Account> getIdAccountMap(List<Account> accounts) { |
account -> account
是一个返回本身的 lambda 表达式。使用 Function.identity()
会使得代码更简洁:
1 | public Map<Long, Account> getIdAccountMap(List<Account> accounts) { |
处理重复 key 的情况
当需要根据可能有重复值(如 name)的字段来创建 map 时,可能会遇到 java.lang.IllegalStateException: Duplicate key
异常。为了处理这种情况,可以给 toMap()
方法传入一个合并函数:
1 | public Map<String, Account> getNameAccountMap(List<Account> accounts) { |
这里只是简单的使用后者覆盖前者。toMap()
还允许我们指定一个具体的 Map
实现类来收集数据:
1 | public Map<String, Account> getNameAccountMap(List<Account> accounts) { |
JDK 8 对 List 和 Map 的排序
Java 8 更新前的 List 排序操作
在使用 Java 8 之前,我们通常需要创建一个匿名内部类来实现排序:
1 | Collections.sort(humans, new Comparator<Human>() { |
使用 Lambda 表达式的 List 排序
Java 8 中,我们可以直接使用 lambda 表达式来定义比较器:
1 | humans.sort((Human h1, Human h2) -> h1.getName().compareTo(h2.getName())); |
还可以进一步简化为:
1 | humans.sort((h1, h2) -> h1.getName().compareTo(h2.getName())); |
或者使用静态方法引用:
1 | humans.sort(Human::compareByNameThenAge); |
以及比较器提供的辅助方法:
1 | Collections.sort(humans, Comparator.comparing(Human::getName)); |
反序排序
Java 8 提供了 Comparator.reversed()
方法来实现反向排序:
1 | humans.sort(comparator.reversed()); |
使用多个条件进行排序
可以使用链式操作来进行多级排序:
1 | humans.sort(Comparator.comparing(Human::getName).thenComparing(Human::getAge)); |
利用 Stream API 进行 List 的过滤和 map 操作
列表的过滤处理
使用 stream().filter()
可以方便地对列表进行过滤:
1 | List<String> list2 = list1.stream().filter(s -> s != "1").collect(Collectors.toList()); |
输出结果是 [2, 3]
。
List 转换为另一个 List
可以利用 stream().map()
来转换列表中的每个元素,然后收集到新的列表中:
1 | List<String> list2 = list1.stream().map(string -> "stream ().map () 处理之后:" + string).collect(Collectors.toList()); |
数据聚合
以下是一些利用 Stream 对列表中的用户对象 User
进行各种数据聚合的方法:
- 求和:
list.stream().mapToDouble(User::getHeight).sum()
此方法返回用户集合中所有用户的身高总和。 - 最大值:
list.stream().mapToDouble(User::getHeight).max()
获取用户对象中的最高身高的用户,返回的是 Optional 类型。 - 最小值:
list.stream().mapToDouble(User::getHeight).min()
返回用户集合中最矮的用户,也是返回 Optional 类型。 - 平均值:
list.stream().mapToDouble(User::getHeight).average()
计算所有用户的身高平均值,并以Optional<Average>
形式返回。
列表拆分与合并
在 Java8 中,我们可以使用 Stream 的方法来完成字符串的拆分以及列表的合并。例如:
- 分割:
Stream.of(string.split(","))
将原始字符串按逗号分割,并将结果转换为 List。 - 拼接:
asList.stream().collect(Collectors.joining())
可以把一个集合里的元素使用指定的符号连接起来,形成一个新的字符串。
提取字段转换成列表
有几种方法可以用于提取 appPermissionVoList
列表中的用户 ID:
- 使用 Stream 的方式:
1
2
3List<String> userIds = appPermissionVoList.stream()
.map(appPermissionVo -> appPermissionVo.getUserId())
.collect(Collectors.toList()); - 使用 Guava 库的
Lists.transform()
方法:1
List<String> usrIds = Lists.transform(appPermissionVoList,appPerm->appPerm.getUserId());
分组
分组操作是一种常见的需求。例如:
1 | Map<String, List<User>> groupBySex = userList.stream() |
此代码将根据用户性别进行列表的分组处理。
HashMap 的 compute
, computeIfAbsent
及 computeIfPresent
这三者都是用来对映射中的元素进行修改。它们的区别在于:
computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
仅当指定的键存在时才执行操作。computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
当给定的键不存在于该映射中,则计算其对应的值,并将其放入该映射中,然后返回该值;否则直接返回当前值。compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
不管是新插入还是已存在的键都可以操作。
1 | Object key2 = map.computeIfAbsent("key", k -> new Object()); |
注意事项与最佳实践
- 性能考虑:尽管 Stream API 提供了极大的便利性,但过度使用可能会导致性能问题。因此,需要权衡代码的可读性和效率。
- 空值检查:在处理数据流时一定要注意可能出现的异常情况,特别是来自外部的数据源可能包含 null 值的情况,这可能导致
NullPointerException
异常。 - 并行流的使用: 对于大数据集,考虑使用并行流来加速计算。但要注意,并非所有场景都适合并行化处理。