文章目录
- 1、通过toMap
- 2、通过groupBy
- 1、复杂一点的写法【不推荐,但可以学习一下】
- 2、简单版写法
- 3、其它:通过list和set
- 4、补充【判断拼接字段的处理⭐】
Java利用stream流,判断列表中对象的某个字段的值是否与其它对象重复;尤其是在批量导入的时候,进行数据的重复性校验时;
通过toMap
、groupBy
可以实现判断一个字段的重复性,还可以判断对象中某几个字段拼接后内容的重复性;
实例:校验学员学号stuNumber
的重复性
[ { "classUuid":"685806c0-4b1e-495f-b3fa-b02f089b7421", "stuUuid":"2c1e85df-2464-4b77-81f5-5f958519c1d8", "stuNumber":"1231", "stuName":"测试学员1", "fee":"100" }, { "classUuid":"685806c0-4b1e-495f-b3fa-b02f089b7421", "stuUuid":"b0632666-8334-4618-b4d1-a6c856b0e522", "stuNumber":"1232", "stuName":"测试学员2", "fee":"100" }, { "classUuid":"685806c0-4b1e-495f-b3fa-b02f089b7421", "stuUuid":"742ce0e3-d1e4-4694-a5bb-68ed7e44b7fc", "stuNumber":"1232", "stuName":"测试学员3", "fee":"100" }]
上面的列表中的数据,stuNumber
为学员学号,应该是唯一存在的;但是测试学员2和测试学员3的学号重复,均为1232;因此,列表中有不符合要求的重复字段数据;
1、通过toMap
转为map,键为要比较的字段,值为该键存在的次数(使用”merge”操作:Integer::sum)
然后利用过滤,获取值大于1的数据
如果数据存在,则表明重复
filter中存放的是留下的元素需满足的条件
//import java.util.Map.Entry;/** 验证列表中是否存在相同的学员学号 */ private void checkIfStuNumDuplicate(List<ClassStuCreateParam> params) throws ServiceException { List<String> list = params.stream() .map(ClassStuCreateParam::getStuNumber) .collect(Collectors.toMap(e -> e, e -> 1, Integer::sum)) .entrySet() .stream() .filter(entry -> entry.getValue() > 1) .map(Entry::getKey) .collect(Collectors.toList()); if (CollectionUtils.isNotEmpty(list)) { LOGGER.warn("stuNumber duplicate: [{}]", list); throw new ServiceException( OrgErrorConst.DUPLICATE_STU_NUMBER, String.format("%s:%s", OrgErrorConst.DUPLICATE_STU_NUMBER_MSG, String.join(",", list))); } }
注:
toMap的第三个参数Integer::sum
表示当键重复时,值所需做的操作,就是将旧值和新值进行相加求和;
其它实例:【遇到重复键的时候,用新值替换旧值】
Map<String, String> courseMap = params.stream() .collect( Collectors.toMap( ClassCreateParam::getCourseUuid, ClassCreateParam::getCourseName, (oldValue, newValue) -> newValue));
【保留旧值,就是将上面代码的->符号后的newValue替换为oldValue】
2、通过groupBy
1、复杂一点的写法【不推荐,但可以学习一下】
// 获取学号列表List<String> stuNumList = classStuCreateParams.stream() .map(ClassStuCreateParam::getStuNumber) .filter(Objects::nonNull) .collect(Collectors.toList());if (CollectionUtils.isNotEmpty(stuNumList)) { Map<String, List<Integer>> stuIdIndexMap = IntStream.range(0, stuNumList.size()) .boxed() .collect(Collectors.groupingBy(stuNumList::get)); List<String> numErr = stuIdIndexMap.values().stream() .map( integers -> { List<Integer> stuErrNum = new ArrayList<>(); if (integers.size() > 1) { for (Integer base : integers) { Integer baseNum = base + 3; stuErrNum.add(baseNum); } String err = stuErrNum.toString(); err = err.substring(1, err.length() - 1); return ("第" + err + "行学号填写重复"); } else { return ""; } }) .filter(StringUtils::isNotBlank) .collect(Collectors.toList()); errList.addAll(numErr); }
- 将要比较的字段提取成列表(eg. 将学员学号提取出来为stuNumList)
- 通过groupBy根据stuNum进行分组
- 根据size大小进行判断
2、简单版写法
/** * 方法作用:筛选出列表中的重复的元素 * * @param orgData 初始字符串列表 * @return List 列表中的重复数据 */ private List<String> getDuplicateData(List<String> orgData) { if (CollectionUtils.isEmpty(orgData)) { return Collections.emptyList(); } Map<String, Long> resMap = orgData.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); return orgData.stream().filter(data -> resMap.get(data) > 1).collect(Collectors.toList()); }
3、其它:通过list和set
说到重复性,其实第一个想到的是集合的特性–互异性;
如下所示,courseCodes
是原有的list数据,courseCodeSet
是去重后的数据;
通过CollectionUtils.subtract
求两个Collection
的差集,可以知道重复的元素是什么;
如果想单纯的判断courseCodes
中是否有重复的,可以直接比较’list’和’set’的大小
// 相同编码的课程不能导入 List<String> courseCodes = teachMergeParams.stream() .map(CourseCreateParam::getCourseCode) .collect(Collectors.toList()); // 校验表单中是否有重复的 Set<String> courseCodeSet = teachMergeParams.stream().map(CourseCreateParam::getCourseCode).collect(Collectors.toSet()); Collection<String> duplicateCourses = CollectionUtils.subtract(courseCodes, courseCodeSet);
方法有很多,灵活使用。
4、补充【判断拼接字段的处理⭐】
这里其实就是测试课程1
有三个教师,分别为测试教师1
、测试教师2
、测试教师3
测试课程2
有两个教师,分别为测试教师2
、测试教师5
入参:
[ { "courseName":"测试课程1", "courseCode":"cskc1", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师1_teac1"], "teacherWorks":[] }, { "courseName":"测试课程2", "courseCode":"cskc2", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师2_teac2"], "teacherWorks":[] }, { "courseName":"测试课程1", "courseCode":"cskc1", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师2_teac2"], "teacherWorks":[] }, { "courseName":"测试课程1", "courseCode":"cskc1", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师3_teac3"], "teacherWorks":[] }, { "courseName":"测试课程2", "courseCode":"cskc2", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师5_teac5"], "teacherWorks":[] }]
函数:
private List<CourseExcelParam> handleAddBatchNotByMacro(List<CourseExcelParam> params) { // 处理一个课程对应多个老师的list List<CourseExcelParam> teachMergeParams = new ArrayList<>(); params.parallelStream() .collect( Collectors.groupingBy( p -> (p.getCourseCode() + p.getCourseName()), Collectors.toList())) .forEach( (id, transFer) -> { transFer.stream() .reduce(CourseExcelParam::merge) .ifPresent( course -> { if (CollectionUtils.size(transFer) == 1) { course.setTeacherWorks( course.getTeacherNames().stream() .map(tw -> tw.split("_")[1]) .collect(Collectors.toList())); course.setTeacherNames( course.getTeacherNames().stream() .map(tw -> tw.split("_")[0]) .collect(Collectors.toList())); } Map<String, String> teacherMap = getTeacherWorkAndUuidMap(course.getTeacherWorks()); course.setTeacherUuids(new ArrayList<>(teacherMap.values())); teachMergeParams.add(course); }); }); return teachMergeParams; }
merge:reduce规约操作要执行的函数
注:
当传给reduce的stream流里面只有一个元素时,要额外处理
if (CollectionUtils.size(transFer) == 1)
;
具体情况具体分析
/** * merge操作里要实现把同一课程的教师工号放到一个列表里 * * @param dto * @return */ public CourseExcelParam merge(CourseExcelParam dto) { List<String> targetWorks = new ArrayList<>(); List<String> targetNames = new ArrayList<>(); if (CollectionUtils.isEmpty(this.teacherWorks)) { this.teacherWorks = this.getTeacherNames().stream().map(tw -> tw.split("_")[1]).collect(Collectors.toList()); this.teacherNames = this.getTeacherNames().stream().map(tw -> tw.split("_")[0]).collect(Collectors.toList()); } if (CollectionUtils.isNotEmpty(dto.getTeacherNames())) { List<String> dtoWorks = dto.getTeacherNames().stream().map(tw -> tw.split("_")[1]).collect(Collectors.toList()); List<String> dtoNames = dto.getTeacherNames().stream().map(tw -> tw.split("_")[0]).collect(Collectors.toList()); targetWorks.addAll(this.teacherWorks); targetWorks.addAll(dtoWorks); targetNames.addAll(this.teacherNames); targetNames.addAll(dtoNames); this.teacherNames = targetNames; this.teacherWorks = targetWorks; } return this; }
最终得到的整合结果:
[ { "courseName":"测试课程1", "courseCode":"cskc1", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师1","测试教师2","测试教师3"], "teacherWorks":["teac1","teac2","teac3"] }, { "courseName":"测试课程2", "courseCode":"cskc2", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师2","测试教师5"], "teacherWorks":["teac2","teac5"] }]
此处,groupingBy
在使用时,以p.getCourseCode() + p.getCourseName()为键,list为值;
形如下面的key-value:
"测试课程1cskc1":[ { "courseName":"测试课程1", "courseCode":"cskc1", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师1"] }, { "courseName":"测试课程1", "courseCode":"cskc1", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师2"] }, { "courseName":"测试课程1", "courseCode":"cskc1", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师3"] }, { "courseName":"测试课程2", "courseCode":"cskc2", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师5"] } ], "测试课程2cskc2":[ { "courseName":"测试课程2", "courseCode":"cskc2", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师2"] }, { "courseName":"测试课程2", "courseCode":"cskc2", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师5"] } ]
- 当
课程名称不同,课程编号同
时,则必定为”错误数据,可能就是课程编号输入重复的数据” - 当
课程名称相同,课程编号同
时,指的就是同一个课程 - 当
课程名称相同,课程编号不同
时,指的就是同名的不同课程 - 当
课程名称不同,课程编号不同
时,指的也是不同课程
再结合业务要求,一个课程配有多个教师时,是新起一行,除教师外,其它信息保持一致;所以他要处理的是同一课程的教师信息的合并操作;结合上述分析的四种情况,只有第2种情况符合要求;所以groupingBy
在使用时,要以p.getCourseCode() + p.getCourseName()
为键;
此外,第一种情况,也是需要处理的;处理的情景如下:
courseMergeParam = [ { "courseName":"测试课程1", "courseCode":"cskc1", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师1","测试教师2","测试教师3"], "teacherWorks":["teac1","teac2","teac3"] }, { "courseName":"测试课程2", "courseCode":"cskc2", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师2","测试教师5"], "teacherWorks":["teac2","teac5"] }, { "courseName":"测试课程3", "courseCode":"cskc2", "courseType":"学科", "courseObj":"成年人", "teacherNames":["测试教师2","测试教师5"], "teacherWorks":["teac2","teac5"] }]
测试课程2和测试课程3这两个课程,不同名但同课程编号;意味着课程编号填写重复。
此时,再针对courseMergeParam
做courseCode
的重复性校验处理