Java8使用Stream流操作List


目录

  • Java8使用Stream流操作List
  • 创建对象
  • 一.分组方法
  • 1.1 groupingBy (常用)
  • 1.2 多级分组
  • 1.3 分组汇总
  • 二.查询的方法
  • 2.1 distinct() 去除重复 (常用)
  • 2.2 limit(long n) 和 skip(long n)
  • 2.3 map(T -> R) (常用) 和 flatMap(T -> Stream)
  • 2.4 filter(T -> boolean)过滤
  • 2.5 findAny() 和 findFirst()
  • 三.排序方法
  • 3.1 sorted() / sorted((T, T) -> int) (常用)
  • 四.判断方法
  • 4.1 anyMatch(T -> boolean)
  • 4.2 allMatch(T -> boolean)
  • 4.3 noneMatch(T -> boolean)
  • 五.统计方法
  • 5.1 counting() 和 count()
  • 5.2 reduce((T, T) -> T) 和 reduce(T, (T, T) -> T)
  • 5.3 mapToInt(T -> int) 、mapToDouble(T -> double) 、mapToLong(T -> long)
  • 5.4 summingInt()、summingLong()、summingDouble() 用于计算总和
  • 5.5 averagingInt()、averagingLong()、averagingDouble()用于计算平均值
  • 5.6 summarizingInt()、summarizingLong()、summarizingDouble()
  • 5.7 BigDecimal类型的统计


创建对象

//user对象
public class UserPO {
    @ApiModelProperty(example = "", required = false, value = "序号")
    private Integer id;
    @ApiModelProperty(example = "", required = false, value = "姓名")
    private String name;
    @ApiModelProperty(example = "", required = false, value = "性别")
    private String sex;
    @ApiModelProperty(example = "", required = false, value = "年龄")
    private Integer age;
    @ApiModelProperty(example = "", required = false, value = "部门")
    private String department;
    @ApiModelProperty(example = "", required = false, value = "工资")
    private BigDecimal salary;
    public UserPO(Integer id, String name, String sex, Integer age, String department, BigDecimal salary) {
    this.id = id;
    this.name = name;
    this.sex = sex;
    this.age = age;
    this.department = department;
    this.salary = salary;
}
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getDepartment() {
        return department;
    }

    public void setDepartment(String department) {
        this.department = department;
    }

    public BigDecimal getSalary() {
        return salary;
    }

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }
    
    @Override
    public String toString() {
        return "UserPO{" +
                "序号=" + id +
                ", 姓名='" + name + '\'' +
                ", 性别='" + sex + '\'' +
                ", 年龄=" + age +
                ", 部门='" + department + '\'' +
                ", 工资=" + salary +
                '}';
    }
}

一.分组方法

1.1 groupingBy (常用)

使用 groupingBy() 将数据进行分组,最终返回一个 Map 类型。
注意 : 集合如果为空,进行分组时会抛异常
例 : 根据部门对用户列表进行分组

/**
 * @Description groupingBy() 分组
 * @Author gzh
 */
@Test
public void groupingByTest(){
    //获取用户列表
    List<UserPO> userList = getUserList();
    //根据部门对用户列表进行分组
    Map<String,List<UserPO>> userMap = userList.stream().collect(Collectors.groupingBy(UserPO::getDepartment));
    //遍历分组后的结果
    userMap.forEach((key, value) -> {
        System.out.println(key + ":");
        value.forEach(System.out::println);
        System.out.println("-------------------");
    });
}

执行结果

java list stream 实体类多个参数分组_java

1.2 多级分组

groupingBy 可以接受一个第二参数实现多级分组。
例 : 根据部门和性别对用户列表进行分组。

/**
 * @Description 使用 groupingBy() 多级分组
 * @Author gzh
 */
 @Test
 public void multistageGroupingByTest() {
     //获取用户列表
     List<UserPO> userList = getUserList();
     //根据部门和性别对用户列表进行分组
     Map<String,Map<String,List<UserPO>>> userMap = userList.stream().collect(Collectors.groupingBy(UserPO::getDepartment,Collectors.groupingBy(UserPO::getSex)));
     //遍历分组后的结果
     userMap.forEach((key1, map) -> {
         System.out.println(key1 + ":");
         map.forEach((key2,user)->{
             System.out.println(key2 + ":");
             user.forEach(System.out::println);
         });
         System.out.println("------------------------------");
     });
 }

执行结果

java list stream 实体类多个参数分组_java_02

1.3 分组汇总

例 : 根据部门进行分组,汇总各个部门用户的平均年龄。

/**
 * @Description 使用 groupingBy() 分组汇总
 * @Author gzh
 */
@Test
public void groupCollectTest()    {
    //获取用户列表
    List<UserPO> userList = getUserList();
    //根据部门进行分组,汇总各个部门用户的平均年龄
    Map<String, Double> userMap = userList.stream().collect(Collectors.groupingBy(UserPO::getDepartment, Collectors.averagingInt(UserPO::getAge)));
    //遍历分组后的结果
    userMap.forEach((key, value) -> {
        System.out.println(key + "的平均年龄:" + value);
    });
}

执行结果

java list stream 实体类多个参数分组_System_03

二.查询的方法

2.1 distinct() 去除重复 (常用)

使用 distinct() 方法可以去除重复的数据。
例 : 获取部门列表,并去除重复数据。

/**
 * @Description 使用distinct()去除重复数据
 * @Author gzh
 */
 @Test
 public void distinctTest() {
     //获取用户列表
     List<UserPO> userList = getUserList();
     //获取部门列表,并去除重复数据
     List<String> departmentList = userList.stream().map(UserPO::getDepartment).distinct().collect(Collectors.toList());
     //遍历部门列表
     System.out.println("------去重前------");
     userList.forEach(item->{
         System.out.println(item.getDepartment());
     });
     System.out.println("------去重后------");
     departmentList.forEach(System.out::println);
 }

执行结果

java list stream 实体类多个参数分组_java_04

2.2 limit(long n) 和 skip(long n)

limit(long n) 方法用于返回前n条数据,skip(long n) 方法用于跳过前n条数据。
例 : 获取用户列表,要求跳过第1条数据后的前3条数据。

/**
 * @Description limit(long n)方法用于返回前n条数据  skip(long n)方法用于跳过前n条数据
 * @Author gzh
 */
@Test
public void limitAndSkipTest() {
    //获取用户列表
    List<UserPO> userList = getUserList();
    //获取用户列表,要求跳过第1条数据后的前3条数据
    userList = userList.stream().skip(1).limit(3).collect(Collectors.toList());
    //遍历用户列表
    userList.forEach(System.out::println);
}

执行结果

java list stream 实体类多个参数分组_list_05

2.3 map(T -> R) (常用) 和 flatMap(T -> Stream)

使用 map() 将流中的每一个元素 T 映射为 R(类似类型转换)。
注意 : 当集合为null时,使用map()获取列元素时不会抛异常
使用 flatMap() 将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流。
例 : 使用 map() 方法获取用户列表中的名称列。

/**
 * @Description 使用map()获取列元素
 * @Author gzh
 */
@Test
public void mapTest()    {
    //获取用户列表
    List<UserPO> userList = getUserList();
    //获取用户名称列表
    List<String> nameList = userList.stream().map(UserPO::getName).collect(Collectors.toList());
    //或者
    List<Integer> gaeList = userList.stream().map(user -> user.getAge()).collect(Collectors.toList());
    //遍历名称列表
    nameList.forEach(System.out::println);
    //遍历年龄
    gaeList.forEach(System.out::println);
}

数组类型

//数组类型
String[] nameArray = userList.stream().map(UserPO::getName).collect(Collectors.toList()).toArray(new String[userList.size()]);

执行结果

java list stream 实体类多个参数分组_list_06

例 : 使用 flatMap() 将流中的每一个元素连接成为一个流。

/**
 * @Description 使用flatMap()将流中的每一个元素连接成为一个流
 * @Author gzh
 */
@Test
public void flatMapTest() {
    //创建城市
    List<String> cityList = new ArrayList<String>();
    cityList.add("北京;上海;广州;深圳");
    cityList.add("苏州;武汉;杭州;南京");
    //分隔城市列表,使用 flatMap() 将流中的每一个元素连接成为一个流。
    cityList = cityList.stream().map(city -> city.split(";")).flatMap(Arrays::stream).collect(Collectors.toList());
    //遍历城市列表
    cityList.forEach(System.out::println);
}

执行结果

java list stream 实体类多个参数分组_System_07

2.4 filter(T -> boolean)过滤

使用 filter() 过滤列表数据。
例 : 获取部门为“研发部”的用户列表。

/**
 * @Description 使用filter()过滤列表信息
 * @Author gzh
 */
@Test
public void filterTest() {
    //获取用户列表
    List<UserPO> userList = getUserList();
    //获取部门为“研发部”的用户列表
    userList = userList.stream().filter(user -> user.getDepartment().equals("研发部")).collect(Collectors.toList());
    //遍历用户列表
    userList.forEach(System.out::println);
}

执行结果

java list stream 实体类多个参数分组_list_08

2.5 findAny() 和 findFirst()

使用 findAny() 和 findFirst() 获取一条数据。
例 : 获取用户名称为“研发部”的用户信息,如果未找到则返回null。

/**
 * @Description 使用findAny()获取第一条数据
 * @Author gzh
 */
@Test
public void findAnytTest() {
    //获取用户列表
    List<UserPO> userList = getUserList();
    //获取用户名称为“研发部”的用户信息,如果没有找到则返回null
    UserPO user = userList.stream().filter(u -> u.getDepartment().equals("研发部")).findAny().orElse(null);
    //打印用户信息
    System.out.println(user);
}

执行结果

java list stream 实体类多个参数分组_list_09

注意:findFirst() 和 findAny() 都是获取列表中的第一条数据,但是findAny()操作,返回的元素是不确定的,对于同一个列表多次调用findAny()有可能会返回不同的值。使用findAny()是为了更高效的性能。如果是数据较少,串行地情况下,一般会返回第一个结果,如果是并行(parallelStream并行流)的情况,那就不能确保是第一个。
例 :使用parallelStream并行流,findAny() 返回的就不一定是第一条数据。

UserPO user = userList.parallelStream().filter(u -> u.getDepartment().startsWith("研发部")).findAny().orElse(null);

执行多次的结果

java list stream 实体类多个参数分组_List_10

三.排序方法

3.1 sorted() / sorted((T, T) -> int) (常用)

如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,如 Stream。反之, 需要调用 sorted((T, T) -> int) 实现 Comparator 接口。
例 : 根据用户年龄进行排序。

/**
 * @Description 使用 sorted() 排序
 * @Author gzh
 */
@Test
public void sortedTest() {
    //获取用户列表
    List<UserPO> userList = getUserList();
    //根据年龄排序(升序)
    userList = userList.stream().sorted(Comparator.comparingInt(UserPO::getAge)).collect(Collectors.toList());
    //根据年龄排序(降序)  reversed()相反的
    List<UserPO> userLists = userList.stream().sorted(Comparator.comparingInt(UserPO::getAge).reversed()).collect(Collectors.toList());
    //遍历用户列表(升序)
    userList.forEach(System.out::println);
    System.out.println("--------------------");
    //遍历用户列表(降序)
    userLists.forEach(System.out::println);
}

执行结果

java list stream 实体类多个参数分组_list_11

四.判断方法

4.1 anyMatch(T -> boolean)

使用 anyMatch(T -> boolean) 判断流中是否有一个元素匹配给定的 T -> boolean 条件。

4.2 allMatch(T -> boolean)

使用 allMatch(T -> boolean) 判断流中是否所有元素都匹配给定的 T -> boolean 条件。

4.3 noneMatch(T -> boolean)

使用 noneMatch(T -> boolean) 流中是否没有元素匹配给定的 T -> boolean 条件。
例 : 使用 anyMatch()、allMatch()、noneMatch() 进行判断。

/**
 * @Description 使用 anyMatch()、allMatch()、noneMatch() 进行判断
 * @Author gzh
 */
@Test
public void matchTest() {
    //获取用户列表
    List<UserPO> userList = getUserList();
    //判断用户列表中是否存在名称为“张三”的数据
    boolean result1 = userList.stream().anyMatch(user -> user.getName().equals("张三"));
    //判断用户名称是否都包含“张三”字段
    boolean result2 = userList.stream().allMatch(user -> user.getName().contains("张三"));
    //判断用户名称是否存在不包含“张三”字段
    boolean result3 = userList.stream().noneMatch(user -> user.getName().contains("张三"));
    //打印结果
    System.out.println(result1);
    System.out.println(result2);
    System.out.println(result3);
}

执行结果

java list stream 实体类多个参数分组_System_12

五.统计方法

5.1 counting() 和 count()

使用 counting() 和 count() 可以对列表数据进行统计。
例 : 使用 count() 统计用户列表信息。

/**
 * @Description 使用 counting() 或 count() 统计
 * @Author gzh
 */
@Test
public void countTest()    {
    //获取用户列表
    List<UserPO> userList = getUserList();
    //统计研发部的人数,使用 counting()方法进行统计
    Long departCount = userList.stream().filter(user -> user.getDepartment().equals("研发部")).collect(Collectors.counting());
    //统计30岁以上的人数,使用 count()方法进行统计(推荐)
    Long ageCount = userList.stream().filter(user -> user.getAge() >= 30).count();
    //统计薪资大于1500元的人数
    Long salaryCount = userList.stream().filter(user -> user.getSalary().compareTo(BigDecimal.valueOf(1500)) == 1).count();
    //打印结果
    System.out.println("研发部的人数:" + departCount + "人");
    System.out.println("30岁以上的人数:" + ageCount + "人");
    System.out.println("薪资大于1500元的人数:" + salaryCount + "人");
}

执行结果

java list stream 实体类多个参数分组_List_13

5.2 reduce((T, T) -> T) 和 reduce(T, (T, T) -> T)

使用 reduce((T, T) -> T) 和 reduce(T, (T, T) -> T) 用于组合流中的元素,如求和,求积,求最大值等。
例 : 使用 reduce() 求用户列表中年龄的最大值、最小值、总和。

/**
 * @Description 使用 reduce() 方法
 * @Author gzh
 */
@Test
public void reduceTest()    {
    //获取用户列表
    List<UserPO> userList = getUserList();
    //用户列表中年龄的最大值、最小值、总和
    int maxVal = userList.stream().map(UserPO::getAge).reduce(Integer::max).get();
    int minVal = userList.stream().map(UserPO::getAge).reduce(Integer::min).get();
    int sumVal = userList.stream().map(UserPO::getAge).reduce(0,Integer::sum);
    //打印结果
    System.out.println("最大年龄:" + maxVal);
    System.out.println("最小年龄:" + minVal);
    System.out.println("年龄总和:" + sumVal);
}

执行结果

java list stream 实体类多个参数分组_List_14

5.3 mapToInt(T -> int) 、mapToDouble(T -> double) 、mapToLong(T -> long)

int sumVal = userList.stream().map(User::getAge).reduce(0,Integer::sum);计算元素总和的方法其中暗含了装箱成本,map(User::getAge) 方法过后流变成了 Stream 类型,而每个 Integer 都要拆箱成一个原始类型再进行 sum 方法求和,这样大大影响了效率。针对这个问题 Java 8 有良心地引入了数值流 IntStream, DoubleStream, LongStream,这种流中的元素都是原始数据类型,分别是 int,double,long。

流转换为数值流:
mapToInt(T -> int) : return IntStream
mapToDouble(T -> double) : return DoubleStream
mapToLong(T -> long) : return LongStream
例 : 使用 mapToInt() 求用户列表中年龄的最大值、最小值、总和、平均值。

/**
 * @Description 使用 mapToInt() 方法
 * @Author gzh
 */
@Test
public void mapToIntTest()    {
    //获取用户列表
    List<UserPO> userList = getUserList();
    //用户列表中年龄的最大值、最小值、总和、平均值
    int maxVal = userList.stream().mapToInt(UserPO::getAge).max().getAsInt();
    int minVal = userList.stream().mapToInt(UserPO::getAge).min().getAsInt();
    int sumVal = userList.stream().mapToInt(UserPO::getAge).sum();
    double aveVal =  userList.stream().mapToInt(UserPO::getAge).average().getAsDouble();
    //打印结果
    System.out.println("最大年龄:" + maxVal);
    System.out.println("最小年龄:" + minVal);
    System.out.println("年龄总和:" + sumVal);
    System.out.println("平均年龄:" + aveVal);
}

执行结果

java list stream 实体类多个参数分组_List_15

5.4 summingInt()、summingLong()、summingDouble() 用于计算总和

例 : 计算年龄总和

//计算年龄总和
int sumAge = userList.stream().collect(Collectors.summingInt(UserPO::getAge));

5.5 averagingInt()、averagingLong()、averagingDouble()用于计算平均值

例 : 计算平均年龄

//计算平均年龄
double aveAge = userList.stream().collect(Collectors.averagingDouble(UserPO::getAge));

5.6 summarizingInt()、summarizingLong()、summarizingDouble()

这三个方法比较特殊,比如 summarizingInt 会返回 IntSummaryStatistics 类型。

IntSummaryStatistics类提供了用于计算的平均值、总数、最大值、最小值、总和等方法,方法如下图:

java list stream 实体类多个参数分组_list_16

例 : 使用 IntSummaryStatistics 统计:最大值、最小值、总和、平均值、总数

/**
 * @Description 使用 summarizingInt 统计
 * @Author gzh
 */
@Test
public void summarizingIntTest()    {
    //获取用户列表
    List<UserPO> userList = getUserList();
    //获取IntSummaryStatistics对象
    IntSummaryStatistics ageStatistics = userList.stream().collect(Collectors.summarizingInt(UserPO::getAge));
    //统计:最大值、最小值、总和、平均值、总数
    System.out.println("最大年龄:" + ageStatistics.getMax());
    System.out.println("最小年龄:" + ageStatistics.getMin());
    System.out.println("年龄总和:" + ageStatistics.getSum());
    System.out.println("平均年龄:" + ageStatistics.getAverage());
    System.out.println("员工总数:" + ageStatistics.getCount());
}

执行结果

java list stream 实体类多个参数分组_List_17

5.7 BigDecimal类型的统计

对于资金相关的字段,通常会使用BigDecimal数据类型。
例 : 统计用户薪资信息。

/**
 * @Description BigDecimal类型的统计
 * @Author gzh
 */
@Test
public void BigDecimalTest(){
    //获取用户列表
    List<UserPO> userList = getUserList();
    //最高薪资
    BigDecimal maxSalary = userList.stream().map(UserPO::getSalary).max((x1, x2) -> x1.compareTo(x2)).get();
    //最低薪资
    BigDecimal minSalary = userList.stream().map(UserPO::getSalary).min((x1, x2) -> x1.compareTo(x2)).get();
    //薪资总和
    BigDecimal sumSalary = userList.stream().map(UserPO::getSalary).reduce(BigDecimal.ZERO, BigDecimal::add);
    //平均薪资
    BigDecimal avgSalary = userList.stream().map(UserPO::getSalary).reduce(BigDecimal.ZERO, BigDecimal::add).divide(BigDecimal.valueOf(userList.size()), 2, BigDecimal.ROUND_HALF_UP);
    //打印统计结果
    System.out.println("最高薪资:" + maxSalary + "元");
    System.out.println("最低薪资:" + minSalary + "元");
    System.out.println("薪资总和:" + sumSalary + "元");
    System.out.println("平均薪资:" + avgSalary + "元");
}

执行结果

java list stream 实体类多个参数分组_System_18