持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情
通过 Java8 新特性之 Stream 学习 文章的学习,对 Java8 引入的 Stream 类也有了初步的认识,这次就来综合使用一下其中的方法,感受 Stream 带来的舒适感!
0. 准备
定义简单的 User 对象,用来对文中使用的方法进行测试
@Data
public class User{
private Long id;
private String name;
private Integer age;
private BigDecimal account;
}
- 如未特殊说明,文中使用的 list 即是代表 User 对象的集合
1. 集合中筛选符合条件的元素或元素集合
Java8 之前,判断或获取集合中包含的指定元素对象时,通常是使用 for 循环遍历的方式,有了 Stream 之后,就可以使用其中的方法来轻松实现判断和获取指定元素的操作。
1.1 判断
判断集合中是否包含元素可以使用的方法有:
-
anyMatch() 方法,如果存在元素匹配条件成功就返回 true,否则返回 false,更为推荐的方式
-
noneMatch() 方法,全部元素都匹配失败返回 true,否则返回 false
-
filter()/findAny()/isPrsent() 配合使用,来判断结果流中是否有满足条件的元素
-
根据 filter() 条件筛选后并将流收集为集合,再根据集合长度判断是否存在
// anyMatch()
boolean result1 = list.stream().anyMatch(a -> a.getName().equals("tom"));
// noneMatch()
boolean result2 = list.stream().noneMatch(a -> a.getName().equals("tom"));
// filter()/findAny()/isPrsent()
boolean result3 = list.stream().filter(a -> a.getName().equals("tom")).findAny().isPrsent();
//
boolean result4 = list.stream().filter(a -> a.getName().equals("tom")).collect(Collectors.toList()).size() > 0;
1.2 获取
从集合中根据条件筛选元素时,可以通过标准的 filter() 方法进行筛选,并将结果流收集为集合;还可以通过类似 for 循环遍历的 foreach() 方法来实现将符合条件的元素加入到指定集合中。
//根据条件筛选为新的集合
List<User> newList1 = list.stream().filter(a -> a.getName().equals("tom")).collect(Collectors.toList());
//遍历集合,如果有满足条件的元素,就进行操作
List<User> newList2 = new Arraylist<>();
list.stream().forEach(a -> {
if ("tom".equals(a.getName())){
newList2.add(a);
}
});
如果想要获取满足条件的元素中的一个,则可以使用 findFirst() 或 findAny() 方法,这两个方法均返回一个 Optional 容器对象,想要获取真实对象则需要从 Optional 中获取。
Optional 容器对象也有学习过,其中的 value 对象代表真实对象内容,尽管 findFirst() 或 findAny() 方法保证了 Optional 对象非空,但是其中的 value 还是可能为空,此时可以配合 Optional 的 orElse()。
// 获取满足条件的 user 对象,如果不存在则返回初始化的新对象
User user = list.stream().filter(a -> "tom".equals(a.getName())).findAny().orElse(new User());
2. 集合中对象的指定属性映射为新的集合
Stream 中提供的 map() 方法应用同样比较广泛,可以通过 map() 将集合映射成为其他需要的数据结构。
2.1 集合元素对象的属性集合
当程序中只需要关注集合中元素对象的某一个属性的集合时,就可以使用 map() 方法进行映射。
// 获取容器中对象的指定属性的集合
list.stream().map(Student::getName).collect(Collectors.toList());
// 去除属性集合中重复的内容
list.stream().map(Student::getName).distinct().collect(Collectors.toList());
2.2 List 转 Map / Array
针对常见 List 容器,可以在操作中转成其他需要的容器类型,如想要将 List 容器转成对应的 Map,其中的 key 使用容器中元素对象的唯一 id。
// List 转 Map
Map<Long,User> userMap = list.stream().collect(Collectors.toMap(User::getId, user -> user));
// List 转 Array,获取元素对象名称的集合,并将该集合转为数组
String[] nameArr = list.stream().map(User::getName).toArray(String[]::new);
2.3 根据条件分组为多个 List
对于 List 转 Map,还有一种常见的场景就是根据集合中元素指定属性分组,得到多个不同的集合,此时可以使用属性值为 key,相应的结果集合为 value。
分组操作发生在流的收集过程中,使用 Collectors.groupingBy() 方法。
// List 根据指定属性分组,转为以该属性为 key 的 Map
Map<String,List<User>> studentMap = list.stream().collect(Collectors.groupingBy(User::getName));
3. 集合中元素或元素属性的计算操作
Stream 实现将集合中元素以流的形式存储,除了可以对元素和元素的属性进行筛选判断,还可以对数字类型的元素或元素属性进行数学计算操作。
3.1 数学方法实现计算
对于常见的对整数类型或金额求和计算,可以使用 Stream 提供的 sum() 方法实现,也可以使用 reduce() 来实现 BigDecimal 类型数字的计算操作。
// sum() 求和,可用于 int、double、long 类型
double sum = list.stream().mapToDouble(User::getAccount).sum();
// BigDecimal 类型使用 reduce() 方法
BigDecimal sum = list.stream().map(User::getAccount).reduce(BigDecimal.ZERO,BigDecimal::add);
3.2 统计分析对象实现计算
如果需要对某些数据进行多种维度的数学计算,可以通过 summaryStatistics() 方法将数字类型收集起来,并计算其最大值、最小值、平均值等统计信息。
Stream 计算时,针对不同类型的数字有对应的实现方法,如 mapToInt()、mapToLong()、mapToDouble() 等。
IntSummaryStatistics resultNum = list.stream().mapToInt((item) -> item.getAge()).summaryStatistics();
// 最大值
resultNum.getMax();
// 最小值
resultNum.getMin();
// 求和
resultNum.getSum();
// 平均值
resultNum.getAverage();
// 数量
resultNum.getCount();
3.3 求和计算时空指针异常问题
Java 中自定义对象的属性通常使用包装类型定义,因此使用 Stream 对集合元素的属性求和时,存在元素属性为 null 的情况,此时求和计算会发生空指针异常。
因此在对集合属性求和时,如果不能保证所有属性不为 null,可以先将属性为 null 的元素过滤掉。
int sum = list.stream().filter(a -> a.getAge != null).mapToInt(User::getAge).sum();