文章目录

  • 1、通过toMap
  • 2、通过groupBy
    • 1、复杂一点的写法【不推荐,但可以学习一下】
    • 2、简单版写法
  • 3、其它:通过list和set
  • 4、补充【判断拼接字段的处理⭐】

Java利用stream流,判断列表中对象的某个字段的值是否与其它对象重复;尤其是在批量导入的时候,进行数据的重复性校验时;

通过toMapgroupBy可以实现判断一个字段的重复性,还可以判断对象中某几个字段拼接后内容的重复性;

实例:校验学员学号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);    }
  1. 将要比较的字段提取成列表(eg. 将学员学号提取出来为stuNumList)
  2. 通过groupBy根据stuNum进行分组
  3. 根据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"]        }    ]
  1. 课程名称不同,课程编号同时,则必定为”错误数据,可能就是课程编号输入重复的数据”
  2. 课程名称相同,课程编号同时,指的就是同一个课程
  3. 课程名称相同,课程编号不同时,指的就是同名不同课程
  4. 课程名称不同,课程编号不同时,指的也是不同课程

再结合业务要求,一个课程配有多个教师时,是新起一行,除教师外,其它信息保持一致;所以他要处理的是同一课程的教师信息的合并操作;结合上述分析的四种情况,只有第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这两个课程,不同名但同课程编号;意味着课程编号填写重复。
此时,再针对courseMergeParamcourseCode的重复性校验处理