对于这两个方法的研究,源于一道比较经典的面试题:“x.equals(y)==true;x,y可有不一样的hashcode对吗?”,其实这道题的关键在于考咱们对equals()方法和hashCode()方法的理解,网上看了很多文章,有说对的,也有说不对的。在我看来对也不对,具体缘由,咱们下面慢慢分析。java
equals()方法是Object中定义的方法,任何类均可以重写,可是须要遵循必定的规范,咱们看一下Object中的默认实现面试
public boolean equals(Object obj) {
return (this == obj);
}复制代码
能够看到就是对两个对象的比较,也就是两个对象的地址值是否相等。然而有的时候,默认的比较方式并不能知足咱们的需求,好比要咱们判断两个学生对象是不是同一个,须要从学号、姓名等方面来比较,这时候就要重写equals()方法了,这个重写规则,JAVA是有明确的规范的:bash
2.1 自反性:x.equals(x)必须为true;
2.2 对称性:x.equals(y)和y.equals(x)返回值必须相等;
2.3 传递性:x.equals(y)为true,和y.equals(z)为true,那么x.equals(z)也必须为true;
2.4 一致性:若是对象x和y在equals()中使用的信息没有改变,那么x.equals(y)的值始终不变;
2.5 非null : x不是null,y是null,那么x.equals(y)必须为false。复制代码
这是Android SDK中Object类的hashCode()方法实现:ide
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* {@link java.util.HashMap}.
* <p>
*/
public int hashCode() {
int lockWord = shadow$_monitor_;
final int lockWordStateMask = 0xC0000000; // Top 2 bits.
final int lockWordStateHash = 0x80000000; // Top 2 bits are value 2 (kStateHash).
final int lockWordHashMask = 0x0FFFFFFF; // Low 28 bits.
if ((lockWord & lockWordStateMask) == lockWordStateHash) {
return lockWord & lockWordHashMask;
}
return System.identityHashCode(this);
}
public static native int identityHashCode(Object x);复制代码
经过注释咱们能够得知一个很重要的信息,那就是hashCode()这个方法主要是为了更好的支持哈希表(如HashMap、HashSet、HashTable等)。说到这,咱们有必要了解一下哈希表的存储原理了:工具
当咱们向哈希表(如HashMap、HashSet、HashTable等)插入一个object时,首先调用hashcode()方法得到该对象的哈希码,经过该哈希码能够直接定位object在哈希表中的位置(通常是哈希码对哈希表大小取余),若是该位置没有对象,那么直接将object插入到该位置;若是该位置有对象(可能有多个,经过链表实现),则调用equals()方法将这些对象与object比较,若是相等,则不须要保存object,不然,将该对象插入到该链表中。性能
这也就解释了,为何equals()相等,那么hashCode()必须相等。由于,若是两个对象的equals()方法返回true,则它们在哈希表中只应该出现一次;若是hashCode()不相等,那么它们会被散列到表中不一样的位置,哈希表中不止出现一次。测试
JAVA建议,若是咱们重写了equals()方法,那么也要重写hashCode()方法,由于默认的hashCode()方法返回的是该对象内存中的地址。仍是上面的例子,咱们进行两个学生的比较,依据是学号和姓名,若是都相等,那么认为是同一个对象,有以下代码:ui
public class Student {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Student student1 = new Student();
student1.setId(1);
student1.setName("name1");
Student student2 = new Student();
student2.setId(1);
student2.setName("name1");
System.out.println(student1.equals(student2));
}
}复制代码
这时候返回的是false,确定不是咱们想要的,因此咱们须要重写equals()方法,让它符合咱们的业务需求,重写后的代码以下:this
public class Student {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (getId() != student.getId()) return false;
return getName() != null ? getName().equals(student.getName()) : student.getName() == null;
}
public class Main {
public static void main(String[] args) {
Student student1 = new Student();
student1.setId(1);
student1.setName("name1");
Student student2 = new Student();
student2.setId(1);
student2.setName("name1");
System.out.println(student1.equals(student2));
}
}
}复制代码
经过重写equals()方法以后,打印结果变成了true,可是这样就完了吗?咱们进行另一种测试:google
public class Main {
public static void main(String[] args) {
Student student1 = new Student();
student1.setId(1);
student1.setName("name1");
Student student2 = new Student();
student2.setId(1);
student2.setName("name1");
System.out.println(student1.equals(student2));
Set<Student> set = new HashSet<>();
set.add(student1);
set.add(student2);
System.out.println("setSize: " + set.size());
}
}复制代码
上面student1.equals(student2)返回的是true,setSize理应返回的结果是1才对,可是返回的确是2,这是由于它们的hashCode是不同的,因此Set集合认为它们是两个不一样的对象,所以添加了两次。这时候hashCode()就排上用场了,咱们须要重写hashCode()方法,这就是为何JDK说,若是重写了equals()方法,必需要重写hashCode()方法的缘由,:
public class Student {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (getId() != student.getId()) return false;
return getName() != null ? getName().equals(student.getName()) : student.getName() == null;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + name.hashCode();
return result;
}
}复制代码
这样才算完事了。
hashCode()方法重写的一些原则:
a.若是重写equals()方法,检查条件“两个对象经过equals()方法判断相等,那么它们的hashCode()也应该相等”是否成立,若是不成立,则重写hashCode()方法。
b.hashCode()不能太简单,不然容易形成hash冲突;
c.hashCode()不能太复杂,不然会影响性能。复制代码
可是通常来讲咱们不须要本身去写,这里有几种便捷的实现方式:
(1) Google的Guava项目里有处理hashCode()和equals()的工具类com.google.common.base.Objects;
(2) Apache Commons也有相似的工具类EqualsBuilder和HashCodeBuilder;
(3) Java 7 也提供了工具类java.util.Objects;
(4) 经常使用IDE都提供hashCode()和equals()的代码生成。复制代码
至此,咱们已经能够解答最开始的问题:“x.equals(y)==true;x,y可有不一样的hashcode对吗?”
严格来讲,若是两个对象x.equals(y)==true,那么它们的hashCode()也要相等,若是不相等,当把它们添加到哈希表中时就会出现数据混乱(如重复添加);可是,因为hashCode()方法并非强制实现的,因此,是可能存在不一样的hashcode的。
若是想写出优秀的代码又不想踩坑就遵循官方的规范吧。