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
的重复性校验处理