Java新特性扩展之List集合操作

java8的新特性使用起来非常的方便,详情参考Java8新特性

1.map-获取list集合中对象的某个属性值

1)需求:现有一个包含用户对象的集合,想只获取这些用户的姓名组成一个集合,如何去做?

2)演示

用户对象如下:

@Data
public class User {
    private Integer id;
    private  String name;
    private String phone;
}

列表如下(演示数据):

List<User> userList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
   User user = new User();
   user.setId(i + 1);
   user.setName("" + (i + 1) + "");
   userList.add(user);
}

第一种方式:遍历用户对象的集合进行获取

List<String> nameList = new ArrayList<>();
userList.stream().forEach(user -> nameList.add(user.getName()));

第二种方式:使用流方式提前数据(推荐)

List<String> nameList = userList.stream().map(User::getName).collect(Collectors.toList());

2.filter-对list集合元素过滤取赋值

1)需求:现有一个包含行政区对象的集合,根据父级id获取所有行政区的父级编码,如何去做?

2)演示

用户对象如下:

列表如下(演示数据):

List<AreaCode> list = new ArrayList<>();
list.add(new AreaCode(1, "湖北省", "101", 0, null));
list.add(new AreaCode(2, "武汉市", "10101", 1, null));
list.add(new AreaCode(3, "黄冈市", "10102", 1, null));
list.add(new AreaCode(4, "洪山区", "1010101", 2, null));
list.add(new AreaCode(5, "江夏区", "1010102", 2, null));
list.add(new AreaCode(6, "江岸区", "1010103", 2, null));
list.add(new AreaCode(6, "阳新县", "1010201", 3, null));

使用get()过滤符合条件的元素

        list.stream().forEach(l -> {
            String code = "0";
            if (l.getParentId() != 0) {
                Optional<AreaCode> any = list.stream().filter(s -> s.getId().equals(l.getParentId())).findAny();
                if(any.isPresent()){
                    AreaCode areaCode = any.get();
                    code = areaCode.getCode();
                }
            }
            l.setParentCode(code);
        });

先对整个集合进行遍历,顶级元素除外,过滤出集合中id是当前元素父Id的元素,将其编码赋值给当前元素的对应属性。看起来有些绕,但对于数据的过滤是非常方便的。需要注意的是,这里必须先使用isPresent()方法进行判断是否有符合要求的数据,否则没有符合要求的数据时直接get会抛出空指针异常。

3.findAny判断list集合对象的字段是否存在某个值

1)需求:现有一个用户对象的集合,判断其中是否包含姓名为张三的用户,如何去做?

2)演示

用户对象如下:

@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private String phone;
}

列表如下(演示数据):

List<User> userList = new ArrayList<>();
userList.add(new User(1, "张三丰", "15645854585"));
userList.add(new User(2, "张三", "15645857858"));
userList.add(new User(3, "李四", "15945854566"));
userList.add(new User(4, "王五", "15755554585"));
userList.add(new User(5, "张三", "15852254585"));

使用isPresent()进行判断

boolean exist1 = userList.stream().filter(user -> "张三".equals(user.getName())).findAny().isPresent();

其返回boolean类型,包含时返回true,不包含时返回false。

当然可以使用get()方法获取此元素的值,其返回的值是第一个符合条件的元素:

User user = userList.stream().filter(user -> "张三".equals(user.getName())).findAny().get();

这里的user内容就是集合元素中的第2个元素(id为2的用户信息)。

4.reduce-数据累加

reduce是一种聚合操作,聚合的含义就是将多个值经过特定计算之后得到单个值,常见的 count 、sum 、avg 、max 、min 等函数就是一种聚合操作。

比如要计算集合的所有发票总金额,就可以简单实现

List<Invoice> list = new ArrayList<>();
list.add(new Invoice(new BigDecimal("123")));
list.add(new Invoice(new BigDecimal("12.97")));
BigDecimal reduce = list.stream().map(Invoice::getAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);

需要注意的是,建议对其进行非空的判断,否则会出现空指针异常。

5.groupingBy-对集合元素进行分组

stream是java8的新特性,其就包含分组的方法。

5.1分组案例一

1)先送上示例需要的数据结构

学生表student

id 学号 姓名 性别 班级编号
1 10202 张三丰 A02
2 10103 李能 A01
3 10203 赵三五 A02
4 20106 黄家豪 C01
5 10109 张慧 A01

班级表clazz

id 班级编号 班级名称
1 A01 计算机1班(本科)
2 A02 计算机2班(本科)
3 C01 计算机1班(专科)

需要是查询当前学生信息及班级名称信息。(仅写java的业务代码)

2)新手写法

    public List<StudentVO> getList() {
        //查询学生列表 实际是需要带查询条件,这里做演示不做条件查询
        List<StudentVO> list = studentDao.selectList();
        if (!CollectionUtils.isEmpty(list)) {
            list.stream().forEach(studentVO -> {
                // 根据班级编码查询班级信息
                ClazzVO clazzVO = clazzService.selectByCode(studentVO.getClazzCode());
                if (clazzVO != null) {
                    //设置班级名称
                    studentVO.setClazzName(clazzVO.getClazzName());
                }
            });
        }
        return list;
    }

在这种写法中,每个学生都需要去查询一次数据库的班级信息,而且有很多学生是同一个班的,其实没必要同一个班级的学生都去查询一次数据库,查询一次就可以了,可以减轻数据库的压力。虽然数据量不会特别大,对数据库的影响也不是很大,但确实值得优化,重要的是有这种思维。

3)优化后的写法

 优化的方式也不少,比如每查询一次班级信息后将信息存入缓存,后续可直接从缓存中获取;又或者先查询出所有的班级信息,然后利用stream的分组特性或filter特性.....

这里以stream的分组特性进行说明:

    public List<StudentVO> getList() {
        //查询学生列表 实际是需要带查询条件,这里做演示不做条件查询
        List<StudentVO> list = studentDao.selectList();
        if (!CollectionUtils.isEmpty(list)) {
            //查询所有班级信息
            List<ClazzVO> clazzList = clazzService.selectAll();
            Map<String, List<ClazzVO>> clazzMap = null;
            if (!CollectionUtils.isEmpty(clazzList)) {
                // 以clazzCode为key进行分组
                clazzMap = clazzList.stream().collect(Collectors.groupingBy(e -> e.getClazzCode()));
            }
            Map<String, List<ClazzVO>> finalClazzMap = clazzMap;
            list.stream().forEach(studentVO -> {
                if (finalClazzMap != null) {
                    //根据编码从内存获取班级信息
                    List<ClazzVO> clazzVOS = finalClazzMap.get(studentVO.getClazzCode());
                    if (!CollectionUtils.isEmpty(clazzVOS)) {
                        //设置班级名称
                        studentVO.setClazzName(clazzVOS.get(0).getClazzName());
                    }
                }
            });
        }
        return list;
    }

虽然代码比优化前多,但相比效率和性能,是值得的。stream分组将班级信息按照编辑编码为key,班级信息为value放入内存中,在遍历学生列表时,可根据班级编码直接从内存中获取班级名称,前后只查询了1次数据库,极大提高了效率,节省了数据库资源。这只是一个示例,说明的是编程的思想,当遇到其他类似的情况时就可以举一反三,一个项目下来可以减少多次不必要的数据库连接,从而提高系统的性能。

5.2分组案例二

上述是对元素进行了分组,实际上也可以对分组后的数据添加序号等等。

下面采用上一小节学生信息进行说明,需求是按照学生班级进行分组,并标注相同班级中每个学生是第几次出现,以及同一个班级中学生的个数。

这里为了演示方便,模拟数据库数据,数据如下:

id 学号 姓名 性别 班级编号
1 10202 张三丰 A02
2 10103 李能 A01
3 10203 赵三五 A02
4 20106 黄家豪 C01
5 10109 张慧 A01
6 10104 赵敏 A01
7 10203 朱倩倩 A02
8 10105 刘明华 A01
9 20106 周强 C01
10 10107 何欢欢 A01

呈上最终的结果

StudentVO(id=1, stuNo=10202, stuName=张三丰, clazzCode=A02, sex=男, time=1, total=3)
StudentVO(id=2, stuNo=10103, stuName=李能, clazzCode=A01, sex=男, time=1, total=5)
StudentVO(id=3, stuNo=10203, stuName=赵三五, clazzCode=A02, sex=男, time=2, total=3)
StudentVO(id=4, stuNo=20106, stuName=黄家豪, clazzCode=C01, sex=男, time=1, total=2)
StudentVO(id=5, stuNo=10109, stuName=张慧, clazzCode=A01, sex=女, time=2, total=5)
StudentVO(id=6, stuNo=10104, stuName=赵敏, clazzCode=A01, sex=女, time=3, total=5)
StudentVO(id=7, stuNo=10203, stuName=朱倩倩, clazzCode=A02, sex=女, time=3, total=3)
StudentVO(id=8, stuNo=10105, stuName=刘明华, clazzCode=A01, sex=男, time=4, total=5)
StudentVO(id=9, stuNo=20106, stuName=周强, clazzCode=C01, sex=男, time=2, total=2)
StudentVO(id=10, stuNo=10107, stuName=何欢欢, clazzCode=A01, sex=女, time=5, total=5)

其中time代表同一个班级中出现的顺序,total代表同一个班级的学生总数。

实现方式如下

    public List<StudentVO> listSort() {
        //查询学生列表 实际是需要带查询条件,这里做演示不做条件查询
        List<StudentVO> list = studentDao.selectList();
        if (!CollectionUtils.isEmpty(list)) {
            // 以clazzCode为key进行分组,收集结果
            Map<String, List<StudentVO>> listMap = list.stream().collect(Collectors.groupingBy(e -> e.getClazzCode()));
            //分组统计个数
            Map<String, Long> count = list.stream().collect(Collectors.groupingBy(e -> e.getClazzCode(), Collectors.counting()));
            for (Map.Entry<String, List<StudentVO>> entry : listMap.entrySet()) {
                Long i = 1L;
                String key = entry.getKey();
                List<StudentVO> values = entry.getValue();
                for (StudentVO value : values) {
                    value.setTotal(count.get(key));     // 总数
                    value.setTime(i++);                 // 次数
                }
            }
        }
        return list;
    }

我比较好奇的是,对于集合分组后,操作分组后的集合数据会影响到原时的集合,这样也简单的不少,但还未弄明白这其中的原理。

6.sorted-对集合元素进行排序

当对多个字段进行排序时,可直接在sql中进行排序,也可以在代码中利用stream进行排序。

下面就对用户信息进行排序,要求是根据更新时间降序,姓名和性别升序排序:

其中实体类为

 排序代码

    public List<User> getList() {
        //查询用户列表 实际是需要带查询条件,这里做演示不做条件查询 根据更新时间降序,姓名和性别升序排序
        List<User> list = userDao.selectList();
        List<User> sortList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(list)) {
            // 按某些字段进行排序
            // 需要注意的是list.stream()对数据进行操作后,原list不会发现变化,要么不使用.stream()方法,要么使用新的集合接收
            sortList = list.stream().sorted(
                    Comparator.comparing(User::getUpdateTime, Comparator.reverseOrder())
                            .thenComparing(User::getName)
                            .thenComparing(User::getSex)
            ).collect(Collectors.toList());
        }
        return sortList;
    }

使用代码根据对象的某些属性排序时,使用stream的sorted方法,再结合 java.util.Comparator comparing()方法,默认是按升序排序,降序时采用reverseOrder()方法即可

posted @ 2022-03-04 23:17  钟小嘿  阅读(1516)  评论(0编辑  收藏  举报