对于 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 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 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 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 方法来达到去重的目的。
帅照: