为什么使用HashSet储存对象时必须重写equals和hashcode方法

xiaoxiao2021-02-27  319

对于 Set 接口的实现类 HashSet,它是按照哈希算法来存取集合中的对象,并且因为其继承了 Set 接口,所以不允许插入相同的数据。但是当我们在储存自定义的类的时候会出现相同的对象,我们来查看下面一个示例。

示例一:

User.java

/** * Created by MGL on 2017/4/22. */ public class User { private String number;//学号 private String name; //姓名 private Integer age; //年龄 public User() { } public User(String number, String name, Integer age) { this.number = number; this.name = name; this.age = age; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "User{" + "number='" + number + '\'' + ", name='" + name + '\'' + ", age=" + age + '}'; } }

测试方法:

Test.java

import java.util.HashMap; import java.util.HashSet; /** * Created by MGL on 2017/4/21. */ public class Test { public static void main(String[] args) { HashSet<User> set = new HashSet<>(); User user1 = new User("123", "zhangsan", 11); User user2 = new User("123", "zhangsan", 11); set.add(user1); set.add(user2); System.out.println(set.size()); } }

此时我们可以看到如下的输出结果:

Set 集合有去重的功能,但是在向 Set 集合中添加自定义的对象时无法去重,我们重写一下 User 类的 equals 和 hashCode 方法(此处的 equals 和 hashCode 为 IDEA 自动生成的)。

/** * Created by MGL on 2017/4/22. */ public class User { private String number;//学号 private String name; //姓名 private Integer age; //年龄 //无参构造函数 public User() { } //3个参数的构造函数 public User(String number, String name, Integer age) { this.number = number; this.name = name; this.age = age; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public boolean equals(Object o) { //判断传入的是否为同一个对象 if (this == o) return true; //判断传入的对象是否为空,是不是相同的类 if (o == null || getClass() != o.getClass()) return false; //强转 User user = (User) o; //判断学号是否相同 if (number != null ? !number.equals(user.number) : user.number != null) return false; //判断姓名是否相同 if (name != null ? !name.equals(user.name) : user.name != null) return false; //判断年龄是否相同 return age != null ? age.equals(user.age) : user.age == null; } /** * Hash值的获取方式 * @return */ @Override public int hashCode() { //获取学号、姓名、年龄的 hashCode 分别乘以 31 int result = number != null ? number.hashCode() : 0; result = 31 * result + (name != null ? name.hashCode() : 0); result = 31 * result + (age != null ? age.hashCode() : 0); return result; } @Override public String toString() { return "User{" + "number='" + number + '\'' + ", name='" + name + '\'' + ", age=" + age + '}'; } }

实例二:

Test02.java:

import java.util.HashSet; /** * Created by MGL on 2017/5/4. */ public class Test02 { public static void main(String[] args) { HashSet<User> set = new HashSet<>(); User user1 = new User("123", "zhangsan", 11); User user2 = new User("123", "zhangsan", 11); set.add(user1); set.add(user2); System.out.println(set.size()); } }

这次存入了一个对象了,这是为什么呢??

在上一篇的文章 HashSet解析 中我们已经知道了 HashSet的内部储存原理其实是 HashMap 虽然在前面的几篇博客中已经介绍了 HashMap 的内部实现,但是还是感觉到了自己的生疏和不足,我们再来贴部分源码来观察一下。

HashSet的 add 方法实际上调用的是 HashMap 的put 方法,我们来看看 HashMap 的 put 方法。

HashMap put 方法:

public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }

HashMap hash方法:

static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }

调用 put 方法时,会调用相应的自定义对象的 hashCode 方法,来获取该对象在 Node 数组中的坐标。因此我们在 User 对象中重写 hashCode 方法是确保相同的对象在同一个 Node 数组中会有相同的坐标。

在储存对象时当发生冲突的时候会经过下面一段代码:

if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))

这段代码首先比较两个对象是不是同一个对象,然后通过 equals 方法判断对象的属性值是否相同,来确认是否为同一个对象。

看看下面的测试用例:

Test03:

import java.util.HashSet; /** * Created by MGL on 2017/5/4. */ public class Test03 { public static void main(String[] args) { HashSet<User> set = new HashSet<>(); User user1 = new User("123", "zhangsan", 11); set.add(user1); set.add(user1); System.out.println(set.size()); } }

其实这两段代码的运行结果是一样的,但是它们的内部运行原理是不相同的,在 Test02 中向 Set 集合中添加了两个不同对象,两个对象的属性值相同,在 Test03 中也是向 Set 集合中添加了两个对象,但是是同一个对象。到底有什么不同,我们在 User 类的 equals 方法出打个断点,调试一下,我们可以发现一个有趣的现象,Test02 可以调用到 equals 而 Test03 不会调用到 equals 这个方法,这到底发生了什么???

其实问题的关键还是在于这一行代码:

if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))

当我们储存同一个对象的时候 (k = p.key) == key 计算结果为 true ,运用双或 的特点,直接就返回了 true ,而 Test03 不是传入的同一个对象,因此会调用 key.equals(k) 这段代码,调用自定义类的 equals 方法。

总结

1.我们在使用 HashSet 对自定义类进行去重的时候,一定要覆盖自定义类的 equals 和 hashCode 方法,hashCode 方法是找到当前对象在 Node 数组重的位置,而 equals 是比较当前对象与对应坐标链表中的对象是否相同。

2.在使用Set对象储存自定义对象的时候,每次都会调用自定义对象的 hashCode 方法,但是 equals 方法并不是每次都会被调用到,不会被调用到的情形有下:

传入同一个对象;当前坐标为空;

3.要根据自己要实现的功能,合理的重写 hashCode 和 equals 方法来达到去重的目的。


帅照:

转载请注明原文地址: https://www.6miu.com/read-2817.html

最新回复(0)