有时候我们从数据库查询到数据之后,想对数据进行一个去重操作,但是从数据库那拿到的是一个对象的集合,这时候我们应该怎么办呢?这里其实就是涉及一个集合流中如何根据某个对象的属性进行去重的问题。接下来我直接用例子进行实现,需要的小伙伴直接拿到代码稍微改一点点就可以了。
1.准备一个实体类
@Data
public class Student{
//学生名字
private String name;
//学生年龄
private int age;
//学生地址
private String address;
//学生所在班级
private String className;
}
2.实战1(不保证顺序)
//模拟从数据库得到数据======START
/**
* [小美,18,北京]
* [大壮,18,北京]
* [大壮,17,河北]
*/
List<Student> students = new ArrayList<>();
{
Student student = new Student();
student.setName("小美");
student.setAge(18);
student.setAddress("北京");
students.add(student);
}
{
Student student = new Student();
student.setName("大壮");
student.setAge(18);
student.setAddress("北京");
students.add(student);
}
{
Student student = new Student();
student.setName("大壮");
student.setAge(17);
student.setAddress("河北");
students.add(student);
}
//模拟从数据库得到数据======END
//根据年龄去重 Comparator.comparing(x->x.getAge())
students = students.stream().collect(Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(x->x.getAge()))), ArrayList::new));
//遍历
students.forEach(student -> {
System.out.println(student);
});
但是这样只能实现去重,无法保证顺序,如果不用在意顺序的话,可以使用该写法。
3.实战2(保证原有顺序)
public static void main(String[] args) {
//模拟从数据库得到数据======START
/**
* [小美,18,北京]
* [大壮,18,北京]
* [大壮,17,河北]
*/
List<Student> students = new ArrayList<>();
{
Student student = new Student();
student.setName("小美");
student.setAge(18);
student.setAddress("北京");
students.add(student);
}
{
Student student = new Student();
student.setName("大壮");
student.setAge(18);
student.setAddress("北京");
students.add(student);
}
{
Student student = new Student();
student.setName("大壮");
student.setAge(17);
student.setAddress("河北");
students.add(student);
}
//模拟从数据库得到数据======END
//根据年龄去重 dis(x->x.getAge())
students = students.stream().filter(dis(x->x.getAge())).collect(Collectors.toList());
//遍历
students.forEach(student -> {
System.out.println(student);
});
}
static <T> Predicate<? super T> dis(Function<? super T,?> key){
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(key.apply(t), Boolean.TRUE) == null;
}
如果看不到上面这个东西的话没事,有空就去研究一下,没空就照抄,不会影响开发的。这里大概说一下基本原理,就是这个filter
方法的参数里是一个断言(Predicate)
,他是一个函数式接口,总的作用大概可以理解为就是底层会拿他来做判断,如果是true,就把元素添加到里头。很多人可能不理解的是这个dis不是会被执行很多次么?其实不是的,集合流只会对这个断言执行一次操作,然后拿着这个Predicate实例对象在那咔咔咔一直做断言。那么实现的具体逻辑留给了我们,这个dis里面的内容就是断言的实现。
有些人可能对lambda表达式一下子看不习惯,下面是dis的匿名内部类实现的不简化写法:
static <T> Predicate<? super T> dis(Function<? super T,?> key){
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return new Predicate<T>() {
@Override
public boolean test(T t) {
return seen.putIfAbsent(key.apply(t), Boolean.TRUE) == null;
}
};
}
底层会把元素拿出来进行遍历断言,查看源码这。