equals()方法和hascode()方法

一、前言

对于这两个方法的研究,源于一道比较经典的面试题:“x.equals(y)==true;x,y可有不同的hashcode对吗?”,其实这道题的关键在于考我们对equals()方法和hashCode()方法的理解,网上看了不少文章,有说对的,也有说不对的。在我看来对也不对,具体原因,我们下面慢慢分析。

二、equals()方法

equals()方法是Object中定义的方法,任何类都可以重写,但是需要遵循一定的规范,我们看一下Object中的默认实现
public boolean equals(Object obj) {
    return (this == obj);
}

可以看到就是对两个对象的比较,也就是两个对象的地址值是否相等。然而有的时候,默认的比较方式并不能满足我们的需求,比如要我们判断两个学生对象是否是同一个,需要从学号、姓名等方面来比较,这时候就要重写equals()方法了,这个重写规则,JAVA是有明确的规范的:

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。 
三、hashCode()方法

这是Android SDK中Object类的hashCode()方法实现:

* 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}. 
*/ 

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()方法返回的是该对象内存中的地址。还是上面的例子,我们进行两个学生的比较,依据是学号和姓名,如果都相等,那么认为是同一个对象,有如下代码:

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;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

}

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));

}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14


这时候返回的是false,肯定不是我们想要的,所以我们需要重写equals()方法,让它符合我们的业务需求,重写后的代码如下:

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;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

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));

}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

}



通过重写equals()方法之后,打印结果变成了true,但是这样就完了吗?我们进行另外一种测试:

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());

}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21



上面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;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40



这样才算完事了。

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的。

三、总结

   如果想写出优秀的代码又不想踩坑就遵循官方的规范吧l(

 
 
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值