1. 程式人生 > >為什麼要重寫hashcode()和equals()方法

為什麼要重寫hashcode()和equals()方法

以JDK1.8原始碼詳解。

一、Object類的hashcode和equals方法

equals方法原始碼:

    /**
     * Indicates whether some other object is "equal to" this one.
     * <p>
     * The {@code equals} method implements an equivalence relation
     * on non-null object references:
     * <ul>
     * <li>It is <i>reflexive</i>: for any non-null reference value
     *     {@code x}, {@code x.equals(x)} should return
     *     {@code true}.
     * <li>It is <i>symmetric</i>: for any non-null reference values
     *     {@code x} and {@code y}, {@code x.equals(y)}
     *     should return {@code true} if and only if
     *     {@code y.equals(x)} returns {@code true}.
     * <li>It is <i>transitive</i>: for any non-null reference values
     *     {@code x}, {@code y}, and {@code z}, if
     *     {@code x.equals(y)} returns {@code true} and
     *     {@code y.equals(z)} returns {@code true}, then
     *     {@code x.equals(z)} should return {@code true}.
     * <li>It is <i>consistent</i>: for any non-null reference values
     *     {@code x} and {@code y}, multiple invocations of
     *     {@code x.equals(y)} consistently return {@code true}
     *     or consistently return {@code false}, provided no
     *     information used in {@code equals} comparisons on the
     *     objects is modified.
     * <li>For any non-null reference value {@code x},
     *     {@code x.equals(null)} should return {@code false}.
     * </ul>
     * <p>
     * The {@code equals} method for class {@code Object} implements
     * the most discriminating possible equivalence relation on objects;
     * that is, for any non-null reference values {@code x} and
     * {@code y}, this method returns {@code true} if and only
     * if {@code x} and {@code y} refer to the same object
     * ({@code x == y} has the value {@code true}).
     * <p>
     * Note that it is generally necessary to override the {@code hashCode}
     * method whenever this method is overridden, so as to maintain the
     * general contract for the {@code hashCode} method, which states
     * that equal objects must have equal hash codes.
     *
     * @param   obj   the reference object with which to compare.
     * @return  {@code true} if this object is the same as the obj
     *          argument; {@code false} otherwise.
     * @see     #hashCode()
     * @see     java.util.HashMap
     */
    public boolean equals(Object obj) {
        return (this == obj);
    }

該equals方法,是一個非空物件引用集合上的等價關係,滿足下面幾點:

1、反身性(reflexive)

對於任意的非空引用x,也就是說x不等於null,有x.equals(x)總是返回true。

2、對稱性(symmetric)

對於任意的非空引用x和y,有x.equals(y)返回true當且僅當y.equals(x)返回true。

3、傳遞性(transitive)

對於任意的非空引用x、y和z,如果x.equals(y)返回true且y.equals(z)返回true,那麼x.equals(z)返回true。

4、一致性(consistent)

對於任意的非空引用x和y,如果用於equals比較的物件沒有被修改的話,那麼,對此呼叫x.equals(y)要麼一致地返回true,要麼一致的返回false。

5、非空性(null)

對於任意的非空引用x,x.equals(null)總是返回false。

 

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}.
     * <p>
     * The general contract of {@code hashCode} is:
     * <ul>
     * <li>Whenever it is invoked on the same object more than once during
     *     an execution of a Java application, the {@code hashCode} method
     *     must consistently return the same integer, provided no information
     *     used in {@code equals} comparisons on the object is modified.
     *     This integer need not remain consistent from one execution of an
     *     application to another execution of the same application.
     * <li>If two objects are equal according to the {@code equals(Object)}
     *     method, then calling the {@code hashCode} method on each of
     *     the two objects must produce the same integer result.
     * <li>It is <em>not</em> required that if two objects are unequal
     *     according to the {@link java.lang.Object#equals(java.lang.Object)}
     *     method, then calling the {@code hashCode} method on each of the
     *     two objects must produce distinct integer results.  However, the
     *     programmer should be aware that producing distinct integer results
     *     for unequal objects may improve the performance of hash tables.
     * </ul>
     * <p>
     * As much as is reasonably practical, the hashCode method defined by
     * class {@code Object} does return distinct integers for distinct
     * objects. (This is typically implemented by converting the internal
     * address of the object into an integer, but this implementation
     * technique is not required by the
     * Java&trade; programming language.)
     *
     * @return  a hash code value for this object.
     * @see     java.lang.Object#equals(java.lang.Object)
     * @see     java.lang.System#identityHashCode
     */
    public native int hashCode();

JDK1.8中hashcode的原始碼不可見。但能看到hashcode以下幾點約定:

1、在程式的一次執行過程中,對同一個物件呼叫hashcode必須一致地返回同一個整數。在同一個程式執行兩次中hashcode產生的整數不必要保持一致。

2、根據equals(Object)方法計算的兩個物件是相等的,那麼兩個物件呼叫hashcode方法會產生相同的整數。

3、根據equals(Object)方法計算的兩個物件不相等,那麼兩個物件呼叫hashcode方法也有可能產生相同的整數,如果產生的整數不同,將會改善雜湊表的效能。

注意:hashCode返回的並不一定是物件的(虛擬)記憶體地址,具體取決於執行時庫和JVM的具體實現。

 

二、為什麼要重寫equals方法

先看一段未重寫equals方法的程式碼:

package com.leboop;

public class EqualsTest {
	public static void main(String[] args) {
		Person p1 = new Person("zhangsan",23);
		Person p2 = new Person("zhangsan",23);
		//輸出false
		System.out.println(p1.equals(p2));
	}
}
package com.leboop;

public class Person {
	private String name;
	private Integer age;

	public Person() {
	}
	
	public Person(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}
	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;
	}

}

主程式中兩個Person例項,按照實際情況具有相同姓名和年齡(僅考慮姓名和年齡)應該是同一個人,而程式判斷不是同一個人,這是因為Java中所有的類都繼承自Object,當然自定義的Person類也不例外。所以p1.equals(p2)呼叫的是父類Object中equals方法,當然不是同一各物件。我們如下重寫equals方法:

@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (age == null) {
			if (other.age != null)
				return false;
		} else if (!age.equals(other.age))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

重寫的equals方法判斷的基本邏輯如下:

(1)如果比較的兩個物件是同一個物件(指引用地址相同),直接返回true;

(2)如果被比較的物件是null,返回false;

(3)如果兩個物件所屬的類不相同,直接返回false;

(4)當前面的條件都不滿足時,將被比較物件轉換成Person物件,然後對姓名和年齡進行比較,如果兩者均相等時,返回true,否則返回false;

所以程式將會輸出true。

 

三、為什麼要重寫hashcode方法

假設如前面已經重寫了equals方法,但是沒有重寫hashcode方法,看如下一段程式碼:

package com.leboop;

public class Person {
	private String name;
	private Integer age;

	public Person() {
	}
	
	public Person(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}
	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;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (age == null) {
			if (other.age != null)
				return false;
		} else if (!age.equals(other.age))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

	
}
package com.leboop;

public class HashCodeTest {
	public static void main(String[] args) {
		Person p1 = new Person("zhangsan",23);
		Person p2 = new Person("zhangsan",23);
		//輸出false
		System.out.println(p1.hashCode()==p2.hashCode());
	}
}

程式會輸出false,認為兩個相等的物件hashcode不相等,這與hashcode方法的約定是矛盾的。所以我們如下重寫hashcode方法:

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((age == null) ? 0 : age.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

重寫執行程式將輸出true。