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()
方法即可