覆盖hashCode方法

覆盖hashCode方法规约:

1、在应用程序运行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一的返回同一个整数。

2、如果两个对象根据equals方法比较是相等的,那么这两个对象的hashCode方法必须返回同样的整数结果。

3、如果两个对象的根据equals方法比较是不相等的,那么hashCode不一定要相等。但是给不相等的对象产生不同的hashCode,有可能提高散列表的性能。

比如下面这个例子:

public class PhoneNumber {
	private final short areaCode;
	private final short prefix;
	private final short lineNumber;
	
	public PhoneNumber(short areaCode, short prefix, short lineNumber) {
		this.areaCode = areaCode;
		this.prefix = prefix;
		this.lineNumber = lineNumber;
	}
	
	@Override
	public boolean equals(Object o) {a
		if(o == this) {
			return true;
		}
		
		if(!(o instanceof PhoneNumber)) {
			return false;
		}
		
		PhoneNumber number = (PhoneNumber)o;
		
		return number.lineNumber == lineNumber &&
				number.prefix == prefix &&
				number.areaCode == areaCode;
	}
}

假设你企图将这个类与HashMap一起使用:

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

m.put(new PhoneNumber(707, 867, 5309), "test");

当你调用m.get(new PhoneNumber(707, 867, 5309)),你期望会返回"test",但是实际返回的却是null;这是因为PhoneNumber没有覆盖hashCode方法,从而导致两个对象拥有不同的散列码,put方法把电话号码放在了一个散列桶中,而get方法却去另外一个散列桶中查找电话号码。

要修正这个问题很简单,只要为PhoneNumber提供一个适当的hashCode方法。

一个好的散列函数通常倾向于“为不相等的对象产生不同的散列码”。理想情况下,散列函数应该把集合中不相等的实例均匀地分布到所有可能的散列值上。下面给出一种简单的生成散列码的方法:

1、把某个非零的常数值,比如说17,保存在一个名为result的int类型的变量中。

2、对于对象中的每个关键域f(指的是equals方法中设计的每个域),完成以下操作:

a.为改域计算int类型的散列码c:

i.如果该域是boolean类型,则计算(f ? 1:0);

ii.如果该域是byte,char,short或者int,则计算(int)f

iii.如果该域是long类型,则计算(int)(f^(f >>> 32))

iv.如果该域是float类型,则计算Float.floatToIntBits(f)

v.如果该域是double类型,则计算Doublr.doubleToLongBits(f),然后按照步骤iii,为得到的long类型值计算散列码

vi.如果该域是一个对象引用,并且该类的equals方法通过递归地调用equals的方式来比较这个域,则同样为这个域递归地调用hashCode。如果这个域的值为0,则返回0vii.如果该域是一个数组,则要把每一个元素当做单独的域来处理。也就是说,递归地应用上述规则,对每个元素计算一个散列码,然后根据2.b中的做法把这些散列码值组合起来。如果数组中的每个元素都很重要,可以利用Arrays.hashCode方法

b.按照下面的公式,把步骤2.a中计算得到的散列码c合并到result中:

result = 31 * result + c;

3、返回result。

4、写完hashCode后,编写单元测试来验证,“相等的实例是否都拥有相同的散列码”。

在散列码的计算中,可以吧冗余域排除在外。冗余域也就是那些可以根据其它域值可以计算出来的域。而且必须排除equals计算中没有用到的任何域。

步骤1中用到的17是一个人选的非零值,用来排除计算出的为0的散列值的影响。

步骤2.b中的乘法部分使得散列值的计算依赖于域的顺序,如果一个类包含多个相似的域,这样的乘法运算就会产生一个更好的散列函数。之所以选择31,是因为它是一个奇素数,一般习惯上使用素数来计算散列结果。

现在我们要把上述的解决办法用到PhoneNumber类中。在它的equals中用到了三个关键域,且都是short类型的:

@Override
public inthashCode() {
	int result = 17;
	result = 31 * result + areaCode;
	result = 31 * result + prefix;
	result = 31 * result + lineNumber;
}

这里不用写(int)areaCode这种的类型转换,是因为result是一个int类型的,表达式中的short会自动转化为int类型进行计算。

如果一个类是不可变的,并且计算散列码的开销也比较大,就应该把散列码缓存在对象内部,而不是每次请求的时候重新计算散列码。可以选择在创建实例时就计算散列码,也可以选择在调用hashCOde时再计算散列码,一切都取决于实际情况。

延迟初始化散列码实例:

private volatile int hashCode;

@Override
public inthashCode() {
	int result = hashCode;
	if(result == 0) {
		result = 17;
		result = 31 * result + areaCode;
		result = 31 * result + prefix;
		result = 31 * result + lineNumber;
		hashCode = result;
	}
	return result;
}

猜你喜欢

转载自cc414011733.iteye.com/blog/2291514
今日推荐