今天来探索一个技术细节:Set能给复杂对象去重吗?
最近做的项目中有这样一个场景:需要把工作流中各个审批环节的人员搜索出来给前端展示。但是工作流存在【提交-打回-再提交】这样的情况,所以按审批环节查询到的人员存在重复的情况,需要给它去重。
这个问题怎么解决?我想到了Set是可以去重的,但是没有在复杂对象的去重中用过,所以来浅究一下。
实例:
先来做个简单的例子试一下
测试一下String类型
public static void main(String[] args) {
Set set = new HashSet();
set.add("A");
set.add("B");
set.add("C");
set.add("B");
System.out.println(set);
}
输出:
[A, B, C]
测试一下基本类型
public static void main(String[] args) {
Set set = new HashSet();
set.add(1);
set.add(2);
set.add(3);
set.add(2);
System.out.println(set);
}
输出:
[1, 2, 3]
测试下复杂对象
@Data
public class HashSetTesst {
private Integer id;
private Boolean isBoy;
private String username;
}
public static void main(String[] args) {
User user1 = new User();
user1.setId(1);
user1.setIsBoy(true);
user1.setUsername("哈哈~");
User user2 = new User();
user2.setId(2);
user2.setIsBoy(false);
user2.setUsername("哈哈~~");
User user3 = new User();
user3.setId(3);
user3.setIsBoy(true);
user3.setUsername("哈哈~~~");
User user4 = new User();
user4.setId(2);
user4.setIsBoy(false);
user4.setUsername("哈哈~~");
Set set = new HashSet();
set.add(user1);
set.add(user2);
set.add(user3);
set.add(user4);
System.out.println(set);
}
输出:
[User(id=1, isBoy=true, username=哈哈~), User(id=2, isBoy=false, username=哈哈~~), User(id=3, isBoy=true, username=哈哈~~~)]
从上面的三个例子可以看出:字符串、基本类型、复杂对象都是可以去重的
原因探究
咱们来看看源码,HashSet是怎么去重的?
先看它的
add
方法,可以看到是使用了HashMap
来存储的:要存储的对象为key,一个空对象PRESENT
作为value
。这个
PRESENT
不是我们关注的重点,咱们略过它,关注作为key的存储对象。我们知道HashMap的key是唯一的,用对象作为key可以去重,但是用复杂对象作为key的话,HashMap里是怎么处理的?再往下看吧
继续点进
HashMap
的put
方法,可以看到这里其实是取了对象的哈希值作key,那么只要对象的哈希值一致就会判定为是同一个对象。关于哈希值的计算原理我们先放过,关注这个问题:复杂对象的哈希值是怎么计算的,两个属性相同的对象哈希值能保持一致吗?
我们的
User
类并没有重写hashcode
方法,所以用的是Object类的hashcode
方法。到
Object
中去看一下:
是一个
native
方法,到这里我已经走不下去了,所以先打住了。
存疑
- 我们使用的jdk版本是 1.8.0_181,复杂对象在属性相同的情况下,哈希值是相同的。这里面的原理暂时没有能力和精力去探究,但是从结果来看确实是相同的;
- 测试使用的User对象其实还是很简单的,如果是更复杂的对象,Set还能对它去重吗?
- 说是探究
Set
的去重原理,其实只针对HashSet
看了下;- 这个版本的jdk,它是怎么计算哈希值的?等下次有时间再来探究吧;
建议
如果有对复杂对象去重的需要,最好是重写该类的
equals(Object obj)
方法和hashCode()
方法,而且这两个方法的返回值必须保持一致:当该类的两个的hashCode()
返回值相同时,它们通过equals()
方法比较也应该返回 true。
参考
java中哈希码有以下约定:
在同一个java程序执行过程中,不论调用hashCode方法多少次,都要返回相同的值, 两个对象的
equals
方法相同,hashCode
方法一定相同, 两个对象的equals
方法不相同,hashCode
方法不一定不同, 两个对象的hashCode
方法不相同,equals
方法一定不同, 两个对象的hashCode
方法相同,equals
方法不一定相同。
hashCode
与equals
方法的关系:1、如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等。 2、如果两个对象不equals,他们的hashcode有可能相等。 3、如果两个对象hashcode相等,他们不一定equals。 4、如果两个对象hashcode不相等,他们一定不equals。