Set能对复杂对象能去重吗?

47 阅读3分钟

今天来探索一个技术细节: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里是怎么处理的?再往下看吧

image-20230304120757490.png

image-20230304120917143.png

继续点进HashMapput方法,可以看到这里其实是取了对象的哈希值作key,那么只要对象的哈希值一致就会判定为是同一个对象。

关于哈希值的计算原理我们先放过,关注这个问题:复杂对象的哈希值是怎么计算的,两个属性相同的对象哈希值能保持一致吗?

image-20230304121334193.png

我们的User类并没有重写hashcode方法,所以用的是Object类的hashcode方法。

Object中去看一下:

image-20230304122851278.png

是一个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方法不一定相同。

hashCodeequals方法的关系:

1、如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等。 2、如果两个对象不equals,他们的hashcode有可能相等。 3、如果两个对象hashcode相等,他们不一定equals。 4、如果两个对象hashcode不相等,他们一定不equals。