1. 程式人生 > >Effective Java Item9-在覆蓋equals方法的同時覆蓋hashCode

Effective Java Item9-在覆蓋equals方法的同時覆蓋hashCode

Effective Java 2nd Edition Reading Notes

Item9: Always override hashCode when overrideing equals

在覆蓋equals方法的同時覆蓋hashCode

每當覆蓋equals方法的時候,一定要覆蓋hashCode方法。

如果沒有如此做的話,那麼將違反hashCode方法的規範,並導致與基於Hash值的類操作時發生錯誤。例如HashSet,HashTable,HashMap。

Object#hashCode()的說明如下:

public int hashCode()

Returns a hash code value for the object. This method is supported for the benefit of hashtables such as those provided by java.util.Hashtable.

The general contract of hashCode is:

Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in 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.

If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the 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 hashtables.

As much as is reasonably practical, the hashCode method defined by class 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 JavaTM programming language.)

1.       如果同一個物件的在equals中使用的屬性沒有發生變化,那麼在一個java應用程式執行期間,對其呼叫hashCode返回的結果必須是相同的整數。

2.       如果兩個物件的equals方法返回true,那麼它們的hashCode方法必須返回相同的整數結果。反之不然。

3.       Object類定義的hashCode方法為特定的物件返回特定的整數。

所以,如果覆蓋了equals方法而沒有覆蓋hashCode方法將違反第二條規定,equal objects have equal hashcode.如果兩個物件通過覆蓋equals方法在邏輯上相等,但是對於hashCode而言,它們是兩個獨立的物件,沒有任何的共同之處。所以hashCode只是返回兩個不同的整數。

例如在下面的例子中,只覆蓋了equals方法,而沒有覆蓋hashCode方法,導致在從HashMap中獲取key對應的value時獲取不到:

package com.googlecode.javatips4u.effectivejava.object;

import java.util.HashMap;

import java.util.Map;

publicclass OverrideHashCode {

publicstaticfinalclass PhoneNumber {

privatefinalshortareaCode;

privatefinalshortprefix;

privatefinalshortlineNumber;

public PhoneNumber(int areaCode, int prefix, int lineNumber) {

                     rangeCheck(areaCode, 999, "area code");

                     rangeCheck(prefix, 999, "prefix");

                     rangeCheck(lineNumber, 9999, "line number");

this.areaCode = (short) areaCode;

this.prefix = (short) prefix;

this.lineNumber = (short) lineNumber;

              }

privatevoid rangeCheck(int arg, int max, String name) {

if (arg < 0 || arg > max)

thrownew IllegalArgumentException(name + ": " + arg);

              }

@Override

publicboolean equals(Object o) {

if (o == this)

returntrue;

if (!(o instanceof PhoneNumber))

returnfalse;

                     PhoneNumber pn = (PhoneNumber) o;

return pn.lineNumber == lineNumber && pn.prefix == prefix

                                   && pn.areaCode == areaCode;

              }

// Broken - no hashCode method!

       }

publicstaticvoid main(String[] args) {

              Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();

              PhoneNumber pn = new PhoneNumber(707, 867, 5309);

              m.put(pn, "Jenny");

              String jenny = m.get(pn);

              String jennyNull = m.get(new PhoneNumber(707, 867, 5309));

              System.out.println(jennyNull);

              System.out.println(jenny);

       }

}

雖然new PhoneNumber(707,867,5309)和pn是equal的,但是由於沒有覆蓋hashCode,導致使用pn以外的key獲取value時,返回null。

HashMap的put方法和get方法如下:

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

returnnull;

    }

public V get(Object key) {

if (key == null)

return getForNullKey();

int hash = hash(key.hashCode());

for (Entry<K,V> e = table[indexFor(hash, table.length)];

e != null;

             e = e.next) {

Object k;

if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

return e.value;

        }

returnnull;

}

從put和get方法可以得知,HashMap的操作是基於key的hashCode值來進行(新增或者查詢)的。

一個簡單的實現hashCode方法:

// worst hashCode override implementation

@Override

publicint hashCode() {

return 10;

}

該方法為所有的物件返回相同的hashCode。良好的hashCode應該使unequal物件的hashCode不相同。

如果使用上面的hashCode實現,那麼上面的程式碼的返回值為:

Jenny

Jenny

編寫hashCode的一個規範:

1. Store some constant nonzero value, say, 17, in an int variable called result.

2. For each significant field f in your object (each field taken into account by the equals method, that is), do the following:

a. Compute an int hash code c for the field:

i. If the field is a boolean, compute (f?1:0).

ii. If the field is a byte, char, short, or int, compute (int) f.

iii. If the field is a long, compute (int) (f ^ (f >>> 32)).

iv. If the field is a float, compute Float.floatToIntBits(f).

v. If the field is a double, compute Double.doubleToLongBits(f), and then hash the resulting long as in step 2.a.iii.

vi. If the field is an object reference and this class’s equals method compares the field by recursively invoking equals, recursively invoke  hashCode on the field. If a more complex comparison is required, compute a “canonical representation” for this field and invoke hashCode on the canonical representation. If the value of the field is null, return 0 (or some other constant, but 0 is traditional).

vii. If the field is an array, treat it as if each element were a separate field. That is, compute a hash code for each significant element by applying these rules recursively, and combine these values per step 2.b. If every element in an array field is significant, you can use one of the Arrays.hashCode methods added in release 1.5.

b. Combine the hash code c computed in step 2.a into result as follows:

        result = 31 * result + c;

3. Return result.

4. When you are finished writing the hashCode method, ask yourself whether equal instances have equal hash codes. Write unit tests to verify your intuition! If equal instances have unequal hash codes, figure out why and fix the problem.

同時,可以將冗餘的欄位排除在計算hash code之外。冗餘的欄位是指可以由其他欄位決定的欄位。

必須將沒有在equals中使用的欄位排除在計算hash code之外。因為這可能導致違反hashCode規範2。

使用一個非0的初始hash code。

@Override

publicint hashCode() {

int result = 17;

       result = 31 * result + areaCode;

       result = 31 * result + prefix;

       result = 31 * result + lineNumber;

return result;

}

如果一個類是不可變類,並且計算hashCode的消耗比較大的話,可以將hashCode快取起來,以免每次都要計算hashCode。如果可以確定類的例項會頻繁的作為Map的hash key,那麼應該在建立例項的時候就計算hashCode,否則的話就在第一次呼叫hashCode的時候進行lazy initialize。

@Override

publicint hashCode() {

int result = hashCode;

if (result == 0) {

              result = 17;

              result = 31 * result + areaCode;

              result = 31 * result + prefix;

              result = 31 * result + lineNumber;

hashCode = result;

       }

return result;

}

不要為了追求效能而排除重要屬性的hash code計算。 hash code的計算效能將影響到HashTable或者HashMap的效率。

轉自:http://blog.csdn.net/sunjavaduke/article/details/4540160