細說equals()方法和hashCode()方法

1、前言

       對於這兩個方法的研究,源於一道比較經典的面試題:「x.equals(y)==true;x,y可有不一樣的hashcode對嗎?」,其實這道題的關鍵在於考咱們對equals()方法和hashCode()方法的理解,網上看了很多文章,有說對的,也有說不對的。在我看來對也不對,具體緣由,咱們下面慢慢分析。java

2、equals()方法

       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複製代碼

3、hashCode()方法

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

3、總結

       若是想寫出優秀的代碼又不想踩坑就遵循官方的規範吧。

相關文章
相關標籤/搜索