在Java中,hashCode()与equals()都是用来比较两个对象是否相等的,那么两者之间有什么区别和联系呢,既然equals()已经实现了此功能,为什么还要用hashCode()呢?此篇文章,就这个问题,简略的说明一下啊,欢迎各位大牛补充。
一、equals()
默认情况下(没有覆盖equals()),equals()都是调用Object类下的equals(),而Object的equals()主要用于判断对象的内存地址引用是不是同一个地址(是不是同一个对象)。
没有覆盖equals()代码如下:
public class Student {
private int age;
private String name;
public Student() {
}
public Student(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
}
测试代码如下:
public class EqualsTest {
public static void main(String[] args) {
List<Student> list = new ArrayList<Student>();
Set<Student> set = new HashSet<Student>();
Student stu1 = new Student(18,"张三");
Student stu2 = new Student(18,"张三");
System.out.println("stu1 == stu2 : "+(stu1 == stu2));
System.out.println("stu1.equals(stu2) : "+stu1.equals(stu2));
list.add(stu1);
list.add(stu2);
System.out.println("list size:"+ list.size());
set.add(stu1);
set.add(stu2);
System.out.println("set size:"+ set.size());
}
}
运行结果如下:
stu1 == stu2 : false
stu1.equals(stu2) : false
list size:2
set size:2
结果分析:
Student类没有覆盖equals(),stu1调用的equals()实际上调用的是Object的equals(),采用的是对象内存地址是否相等来判断对象是否相等。因为这两个对象都是新的对象,所以内存地址是不相等的,所以stu1.equals(stu2)是false。
覆盖equals()代码如下:
public class Student {
private int age;
private String name;
public Student() {
}
public Student(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
运行结果如下:
stu1 == stu2 : false
stu1.equals(stu2) : true
list size:2
set size:2
结果分析:
因为Student两个对象的age属性和name属性相等,而且又是通过覆盖的equals()来判断,所以stu1.equals(stu2)为true。
二、hashCode()
在Object类中有一个方法,public native int hashCode()。哈希码相比大家都不陌生,哈希码并非是唯一的,它是一种算法,让同一个类的对象按照自己不用的特征尽量拥有不同的哈希码,但是这并不代表不同的对象所拥有的哈希码一定完全不同,也会存在相同的情况,所以这个取决于程序员如果写哈希码的算法。
几个常用的哈希码的算法:
1、Object类的hashCode:返回对象的内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。
2、String类的hashCode:根据String类包含的字符串的内容,根据一种特殊的算法返回哈希码,只要字符串所在的堆空间一致,返回的哈希码也一样。
3、Integer类的hashCode:返回的哈希码就是Integer对象里所包含的那个整数的值,例如Integer i1 = new Integer(10),i1.hashCode的值就是10。所以,2个一样大小的Integer对象,返回的哈希码也一样。
hashCode方法的主要作用就是为了配合基于散列的集合一起正常运行。这样的散列的集合包含了HashSet、HashMap、HashTable。
可以考虑这样的一种情况,当我们想集合中插入对象的时候,如何判定集合中是否已经存在该对象?
也许绝大多数的人都会想到调用equals()来逐个的进行比较,这个方法不是不行,也是一种可行的方案,但是如果在集合中已经存在了一万条,十万条甚至更多的数据的时候,如果采用equals()方法去逐一的比较,那必然会造成的是一个需要考虑的另外一个问题,那就是效率问题。此时,hashCode()的作用就有用了,如果在集合中没有该hashCode的值,那简单了,直接存进去,不用再进行任何的比较。如果存在了该hashCode的值,则再去调用equals()与新的元素进行比较,如果一致的话,就不存了,如果不一致,那就散列它的地址,所以这里还存在一个冲突解决的问题。这样一来的话,实际调用equals()的次数就会大大地降低,说的大白话一点,就是说:Java中的hashCode()就是根据一定的规则将与对象有关的信息(比如对象的存储地址、对象的属性等等)映射成一个数值,这个数值被称作为散列值。
延续使用上面的代码,此次覆盖hashCode()并不覆盖equals(),hashCode码是通过age和name生成的。
覆盖hashCode()后的Student类:
public class Student {
private int age;
private String name;
public Student() {
}
public Student(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
}
运行结果:
stu1 == stu2 : false
stu1.equals(stu2) : false
list size:2
hashCode :786954
hashCode :786954
set size:2
结果分析:
此次只覆盖了hashCode()并没有覆盖equals(),两个对象虽然hashCode一样,但是在将stu1和stu2放入set集合时由于equals()比较两个对象是false,所以就没有再比较两个对象的hashCode值。
覆盖equals()和hashCode():
public class Student {
private int age;
private String name;
public Student() {
}
public Student(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
System.out.println("hashCode : "+ result);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
运行结果:
stu1 == stu2 : false
stu1.equals(stu2) :true
list size:2
hashCode :786954
hashCode :786954
set size:1
结果分析:
stu1和stu2通过equals()比较相等,而且返回的hashCode值一样,所以放入set集合中时只放入了一个。
equals()相等,hashCode值不相等代码如下:
public class Student {
private int age;
private String name;
private static int index=5;
public Student() {
}
public Student(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (age+index++);
result = prime * result + ((name == null) ? 0 : name.hashCode());
System.out.println("result :"+result);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
运行结果:
stu1 == stu2 : false
stu1.equals(stu2) : true
list size:2
hashCode :776098
hashCode :776129
set size:2
结果分析:
虽然stu1和stu2通过equals()比较相等,但两个对象的hashCode值不相等,所以在将stu1和stu2放入set集合时认为是两个不同的对象。
修改stu1的某个属性值:
public class Student {
private int age;
private String name;
public Student() {
}
public Student(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
System.out.println("hashCode : "+ result);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
测试代码:
public class EqualsTest {
public static void main(String[] args) {
List<Student> list = new ArrayList<Student>();
Set<Student> set = new HashSet<Student>();
Student stu1 = new Student(3,"张三");
Student stu2 = new Student(3,"张三");
System.out.println("stu1 == stu2 : "+(stu1 == stu2));
System.out.println("stu1.equals(stu2) : "+stu1.equals(stu2));
list.add(stu1);
list.add(stu2);
System.out.println("list size:"+ list.size());
set.add(stu1);
set.add(stu2);
System.out.println("set size:"+ set.size());
stu1.setAge(34);
System.out.println("remove stu1 : "+set.remove(stu1));
System.out.println("set size:"+ set.size());
}
}
运行结果:
stu1 == stu2 : false
stu1.equals(stu2) : true
list size:2
hashCode : 775943
hashCode : 775943
set size:1
hashCode : 776904
remove stu1 : false
set size:1
结果分析:
当我们将某个对象存入set中时,如果该对象的属性参与了hashCode值的计算,那么以后就不能修改该对象参与hashCode计算的那些属性,否则会引起意想不到的错误,正如上述代码中,不能移除stu1对象。
总结:
1、equals()用于比较对象的内容是否相等(覆盖之后);
2、hashCode()只有在集合中用到;
3、覆盖equals()时,比较对象相等将通过覆盖后的equals()进行比较,判断对象的内容是否相等;
4、将对象放入集合时,首先判断要放入对象的hashCode值与集合中任意一个元素的hashCode值是否相等,如果不相等,则直接将该对象放入集合中,如果hashCode值相等,再通过equals()判断要放入的对象与集合中的任意一个对象是否相等,如果equals()判断不相等,直接将该元素放入到集合中,否则不放入。
另外,有些人会误以为,默认情况下,hashCode返回的就是对象的存储地址,事实上这种看法是不全面的,有些JVM在实现时是直接返回对象的存储地址,但是大多数的时候并不是这样的,只能说可能存储地址有一定的关联。
有些人可能又会问,可以直接根据hashCode值判断两个对象是否相等吗?答案肯定是不可以的。因为不同的对象可能会生成相同的hashCode值。虽然我们不能根据hashCode值判断两个对象是否相等,但是可以直接根据hashCode值判断两个对象不等,如果两个对象的hashCode值不等,则必定是两个不同的对象。如果要判定两个对象是否真正的相等,则必须通过equals()。也就是说,对于两个对象:
1、如果equals()得到的结果为true,则两个对象的hashCode值必定相等;
2、如果equals()得到的结果为false,则两个对象的hashCode值不一定不同;
3、如果两个对象的hashCode值不等,则equals()得到的结果必定为false;
4、如果两个对象的hashCode值相等,则equals()得到的结果是未知的。
附录两段代码:
A:HashMap的put()实现:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
B:HashSet的add()实现:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
最后,欢迎程序员的朋友大家互相交流。