重载 hashCode()

在明白了HashMap 具有哪些功能之后,学习如何写一个 hashCode()会更有意义。

 

首先,你无法控制bucket 数组的索引值的产生。这个值依赖于具体的 HashMap 对象的

容量,而容量的改变与负载因子和容器有多满有关。hashCode()生成的结果,经过处理后

成为“桶”的索引(在 SimpleHashMap 中,只是对其取模,模数为 bucket 数组的大小)。

 

设计 hashCode()时最重要的因素就是:无论何时,对同一个对象调用 hashCode()都应

该生成同样的值。如果在将一个对象用 put()添加进 HashMap 时,产生一个 hashCode()

值,而用get()取出时,却产生了另一个 hashCode()值,那么你就无法重新取得该对象了。

所以,如果你的hashCode()依赖于对象中易变的数据,用户就必须当心了,因为此数据

发生变化时,hashCode()就会生成一个不同的散列码,相当于产生了一个不同的“键”。

 

此外,你也不应该使hashCode()依赖于具有唯一性的对象信息。尤其是使用 this 的值,

这只能作出很糟糕的hashCode()。因为你无法生成一个新的“键”,使之与 put()中原始

的“键值对”中的“键”相同。这正是 SpringDetector.java 的问题所在,因为它默认的

hashCode()使用的是对象的地址。所以,你应该使用对象内有意义的识别信息。

 

下面以 String 类为例。String 有个特点:如果程序中有多个 String 对象,都包含相同的

字符串序列,那么这些 String 对象都映射到同一块内存区域(附录 A 详细描述此机制)。

所以 newString(“hello”)生成的两个实例,虽然是相互独立的,但是对它们使用

hashCode()应该生成同样的结果。通过下面的程序可以看到这种情况:

 

//:c11:StringHashCode.java

import com.bruceeckel.simpletest.*;

 

public   class StringHashCode { 

private   static Test monitor = new Test();

public   static    void main(String[] args) {

    System.out.println("Hello".hashCode());

    System.out.println("Hello".hashCode());

    monitor.expect(new String[] {

"69609650",

"69609650"

    });

  }

}   ///:~

 

对于 String 而言,hashCode()明显是基于 String 的内容的。

 

因此,要想使hashCode()实用,它必须速度快,并且必须有意义。也就是说,它必须基

于对象的内容生成散列码。记得吗,散列码不必是独一无二的(应该更关注生成速度,而

不是唯一性),但是通过 hashCode()和 equals(),必须能够完全确定对象的身份。

 

因为在生成bucket 的索引前,hashCode()还需要进一步的处理,所以散列码的生成范围

并不重要,只要是int 即可。

 

还有另一个影响因素:好的 hashCode()应该产生分布均匀的散列码。如果散列码都集中

在一块,那么HashMap 或者 HashSet 在某些区域的负载会很重,就不会有分布均匀的散

列函数那么快。

 

EffectiveJava (Addison-Wesley2001)           这本书中,Joshua Bloch 为怎样写出一份

像样的 hashCode(),给出了基本的指导:

 

1. 给 int 变量 result 赋予某个非零值常量,例如 17。

2.为对象内每个有意义的属性 f(即每个可以作 equals()操作的属性)计算出一个

int 散列码 c:


 

 

域类型

boolean


 

c = (f ? 0 : 1)


 

计算


byte,char, short, or     c = (int)f

int


long

float

double


c = (int)(f ^ (f >>>32))

c = Float.floatToIntBits(f);

long l = Double.doubleToLongBits(f);

c = (int)(l ^ (l >>> 32))


Object,    其 equals() c = f.hashCode( )

调用这个域的

equals( )

数组


 

 

 

对每个元素应用上述规则


 

 

1. 合并计算得到的散列码:

result = 37 *result + c;

2.返回 result

3.检查 hashCode()最后生成的结果,确保相同的对象有相同的散列码。

 

下面便是遵循这些指导的一个例子:

 

//:c11:CountedString.java

// Creating agood hashCode().

import com.bruceeckel.simpletest.*;

import java.util.*;

 

public   class CountedString {

private   static Test monitor = new Test();

private   static List created = new ArrayList();

private String s;

private   int id = 0;

public CountedString(String str) {

    s = str;

    created.add(s);

    Iterator it = created.iterator();

// Id is thetotal number of instances

// of thisstring in use by CountedString:

while(it.hasNext())

if(it.next().equals(s))

        id++;

  }

public String toString() {

return   "String: " + s + " id: " + id +

"hashCode(): "+ hashCode();

  }

public   int hashCode() {

// Very simpleapproach:

// returns.hashCode() * id;

// Using JoshuaBloch's recipe:

int result = 17;

    result = 37*result + s.hashCode();

    result = 37*result + id;

return result;

  }

public   boolean equals(Object o) {

return (o    instanceof CountedString)



 

 

 

      && s.equals(((CountedString)o).s)

      && id == ((CountedString)o).id;

  }

public   static    void main(String[] args) {

    Map map = new HashMap();

    CountedString[] cs =     new CountedString[10];

for(int i = 0; i < cs.length; i++) {

      cs[i] = new CountedString("hi");

      map.put(cs[i], new Integer(i));

    }

    System.out.println(map);

for(int i = 0; i < cs.length; i++) {

      System.out.println("Looking up " + cs[i]);

      System.out.println(map.get(cs[i]));

    }

    monitor.expect(new String[] {

"{String:hi id: 4 hashCode(): 146450=3," +

" String:hi id: 10 hashCode(): 146456=9," +

" String:hi id: 6 hashCode(): 146452=5," +

" String:hi id: 1 hashCode(): 146447=0," +

" String:hi id: 9 hashCode(): 146455=8," +

" String:hi id: 8 hashCode(): 146454=7," +

" String:hi id: 3 hashCode(): 146449=2," +

" String:hi id: 5 hashCode(): 146451=4," +

" String:hi id: 7 hashCode(): 146453=6," +

" String:hi id: 2 hashCode(): 146448=1}",

"Lookingup String: hi id: 1 hashCode(): 146447",

"0",

"Lookingup String: hi id: 2 hashCode(): 146448",

"1",

"Lookingup String: hi id: 3 hashCode(): 146449",

"2",

"Lookingup String: hi id: 4 hashCode(): 146450",

"3",

"Lookingup String: hi id: 5 hashCode(): 146451",

"4",

"Lookingup String: hi id: 6 hashCode(): 146452",

"5",

"Lookingup String: hi id: 7 hashCode(): 146453",

"6",

"Lookingup String: hi id: 8 hashCode(): 146454",

"7",

"Lookingup String: hi id: 9 hashCode(): 146455",

"8",

"Lookingup String: hi id: 10 hashCode(): 146456",

"9"

    });

  }

}   ///:~

 

CountedString 由一个 String 和一个 id 组成,此 id 代表包含相同 String 的

CountedString 对象的编号。所有的 String 都被存储在 static ArrayList 中,构造器中

使用迭代器遍历此ArrayList,此时完成对 id 的计算。

 

hashCode()和 equals()都基于 CountedString 的这两个属性来生成结果;如果它们只

基于 String 或者只基于 id,不同的对象就可能产生相同的值。

 

在 main()中,使用相同的 String 创建了一串 CountedString 对象。以此说明,虽然 String

相同,但是由于id 不同,所以使得它们的散列码并不相同。在程序中,HashMap 被打印

了出来,因此你可以看到它内部是如何存储元素的(以无法辨别的次序),然后程序单独

查询每一个“键”,以此证明查询机制工作正常。

 

为你的类编写正确的hashCode()和equals()是很需要技巧的。Apache的“Jakarta

Commons”项目中有许多工具可以帮助你,可在jakarta.apache.org/commons

“lang”下面找到。


你可能感兴趣的:(重载 hashCode())