hashcode()和equals()的联系
当我们往Collection集合中添加 8大基本类型和String类型的时候,不需要重写hashCode()和equals()方法。因为任何对象都是Object类的子类,所以任何对象都拥有这个方法。
hashCode介绍
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。
equals介绍
equals它的作用也是判断两个对象是否相等,如果对象重写了equals()方法,比较两个对象的内容是否相等;如果没有重写,比较两个对象的地址是否相同,价于“==”。同样的,equals()定义在JDK的Object.java中,这就意味着Java中的任何类都包含有equals()函数。
对于没有用到散列表的情况:
equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。
使用equal()是一定可靠的,为什么还要重写hashCode()呢?
重写hasCode()可以提高检测效率: 程序会先使用 hasCode()方法,hasCode()的结果不同,可以说明两个对象不同,hasCode()若结果相同,不能一定说明对象相同,还要通过比较equal()再判断。由于需要大量并且快速的对比的话如果都用equal()去做显然效率太低 ,所以重写了hasCode()可以大大提升效率。
这种大量的并且快速的对象对比一般使用的hash容器中,比如hashset,hashmap,hashtable等等,比如hashset里要求对象不能重复,则他内部必然要对添加进去的每个对象进行对比,而他的对比规则就是像上面说的那样,先hashCode(),如果hashCode()相同,再用equal()验证,如果hashCode()都不同,则肯定不同,这样对比的效率就很高了。
public class Demo006ArrayList {
public static void main(String[] args) {
// List ArrayList LinkedList
List<Student> stus = new ArrayList<>();
Student s1 = new Student("李思",19, false);
stus.add(s1);
stus.add(new Student("张三",18, false));
stus.add(new Student("宝玉",28, true));
System.out.println("链表长度:"+stus.size());
System.out.println("链表内容:"+stus);
// 会先使用 hashCode() 方法,在hashCode 相同的基础上再通过equals 判断是否相同。
// 两个判断都相同时,才认定元素相等,contains 返回 true
//
System.out.println("是否存在s1:"+stus.contains(s1));
Student s2 = new Student("张三", 18, false);
System.out.println("是否存在s2:"+stus.contains(s2));
System.out.println("s1的hashcode:"+stus.get(1).hashCode());
System.out.println("s1的hashcode:"+s2.hashCode());
}
}
public class Student {
private String name;
private Integer age;
private Boolean gender;
public Student() {
}
public Student(String name, Integer age, Boolean gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
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;
}
public Boolean getGender() {
return gender;
}
public void setGender(Boolean gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name) &&
Objects.equals(age, student.age) &&
Objects.equals(gender, student.gender);
}
/**
* 写得和属性相关,尽可能让那些属性不同的对象 hash值不同
* @return
*/
@Override
public int hashCode() {
// return (name.hashCode()+age*2 + (gender?1:0))/3;
return 1;
}
}
上面重写了两个方法hashCode()和equal()。
假如没有重写,可以看到一个是true,一个是false,就会调用hashCode()和equal()就都是返回当前对象的地址,没有比较意义。
返回结果:
链表长度:3
链表内容:[Student{name=‘李思’, age=19, gender=false}, Student{name=‘张三’, age=18, gender=false}, Student{name=‘宝玉’, age=28, gender=true}]
是否存在s1:true
是否存在s2:false
s1的hashcode:1163157884
s1的hashcode:1956725890
只重写hasCode()不行,可以看到一个是true,一个是false,后面的equal()会调用父类的方法,效果和==一样,比较的是地址,不是我们想要的比较。
结果如下:
链表长度:3
链表内容:[Student{name=‘李思’, age=19, gender=false}, Student{name=‘张三’, age=18, gender=false}, Student{name=‘宝玉’, age=28, gender=true}]
是否存在s1:true
是否存在s2:false
s1的hashcode:12
s1的hashcode:12
只重写equal()可以,可以看到一个是true,另一个也是true,但效率低,再重写hashCode(),提高效率。
链表长度:3
链表内容:[Student{name=‘李思’, age=19, gender=false}, Student{name=‘张三’, age=18, gender=false}, Student{name=‘宝玉’, age=28, gender=true}]
是否存在s1:true
是否存在s2:true
s1的hashcode:1163157884
s1的hashcode:1956725890
对于使用散列表的情况:
public class SetRemo {
public static void main(String[] args) {
Set<Student> hs=new HashSet<>();
Student a1 = new Student("a","12");
hs.add(a1);
Student a2 = new Student("a","12");
hs.add(a2);
hs.add(new Student("b", "12"));
hs.add(new Student("c", "13"));
System.out.println("a1哈希值:"+a1.hashCode());
System.out.println("a2哈希值:"+a2.hashCode());
System.out.println("a1和a2equal布尔值:"+a1.equals(a2));
System.out.println("是否存在name:a age:12:"+hs.contains(new Student("a","12")));
for (Object obj : hs) {
if(obj instanceof Student) {
Student stu=(Student)obj;
System.out.println(stu.getName()+stu.getAge());
}
}
}
}
class Student{
private String name;
private String age;
private boolean gender;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public Student(String name, String age) {
this.name = name;
this.age = age;
}
public Student() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return gender == student.gender &&
Objects.equals(name, student.name) &&
Objects.equals(age, student.age);
}
// @Override
// public int hashCode() {
// // TODO Auto-generated method stub
// return this.getName().hashCode()+this.getAge().hashCode();
// }
}
只重写equal():
// 运行结果
a1哈希值:1163157884
a2哈希值:1956725890
a1和a2equal布尔值:true
是否存在name:a age:12:false
a12
c13
a12
b12
只重写hashCode():
// 运行结果
a1哈希值:1666
a2哈希值:1666
a1和a2equal布尔值:false
是否存在name:a age:12:false
a12
a12
b12
c13
同时重写hashCode()和equal():
// 运行结果:
a1哈希值:1666
a2哈希值:1666
a1和a2equal布尔值:true
是否存在name:a age:12:true
a12
b12
c13
上面用到了散列表HashSet(), 但只重写了equal()方法,equal()给的结果是true,如果按照之前的思路,集合里不会出现重复的a12,但这里出现了。
原因:两个a12的hashCode()值不同,就认为是不同的对象,加入到了集合中去了,所以还要重写hashCode()方法。
可以得知Set必须同时同时重写hashCode()和equal().
总结:
第一个例子重点在于判断是否相同,那么有equal()被重写就够了,hashcode()可以不重写 。
第二个例子在于判断是否不同,由于先通过hashcode()判断是否不同,没有重写的话,一定就是不同,就会加入进去,即使重写了hashcode(),结果2个对象的hashcode相同,大概率说明了2个对象属性相同,但有可能在小概率上2个对象属性不同,却碰巧计算到了相同的hashcode,那么还要判断equal(),没有重写的话,一定就是不同,仍然会加入进去,所以还要重写equal(),说明2个对象相同属性,这样重复属性的对象就不会加入进去了。这种情况下hashcode()和equal()都必须重写。
综合上面2种情况,为了规范,hashcode()和equal()都应该重写。