Java8 新特性之 Stream 的实用操作

1,037 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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 判断

判断集合中是否包含元素可以使用的方法有:

  1. anyMatch() 方法,如果存在元素匹配条件成功就返回 true,否则返回 false,更为推荐的方式

  2. noneMatch() 方法,全部元素都匹配失败返回 true,否则返回 false

  3. filter()/findAny()/isPrsent() 配合使用,来判断结果流中是否有满足条件的元素

  4. 根据 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();