Effective Item 6 - 覆盖equals方法时不要忘记hashCode方法

任何覆盖了equals方法的类都需要覆盖hashCode方法。

忽视这一条将导致类无法与基于散列的数据结构一起正常工作,比如和HashMap、HashSet和Hashtable。

下面是hashCode相关规范:

·在程序执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这个对象调用多少次hashCode,起结果必须始终如一地返回同一个证书。

如果是同一个程序执行多次,每次调用的结果可以不一致。

·如果两个对象根据equals方法比较是相等的,那么两个对象的hashCode结果必须相同。

·如果两个对象根据equals方法比较是不相等的,那么这两个对象的hashCode不一定返回不同的结果。

但是,如果不同的对象返回不同的hashCode,则能提高散列表的性能。

下面的代码就是一个反面例子:

// Shows the need for overriding hashcode when you override equals - Pages 45-46

import java.util.HashMap;
import java.util.Map;

public final class PhoneNumber {
	private final short areaCode;
	private final short prefix;
	private final short lineNumber;

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

	private static void rangeCheck(int arg, int max, String name) {
		if (arg < 0 || arg > max)
			throw new IllegalArgumentException(name + ": " + arg);
	}

	@Override
	public boolean equals(Object o) {
		if (o == this)
			return true;
		if (!(o instanceof PhoneNumber))
			return false;
		PhoneNumber pn = (PhoneNumber) o;
		return pn.lineNumber == lineNumber && pn.prefix == prefix
				&& pn.areaCode == areaCode;
	}

	// Broken - no hashCode method!

	// A decent hashCode method - Page 48
	// @Override public int hashCode() {
	// int result = 17;
	// result = 31 * result + areaCode;
	// result = 31 * result + prefix;
	// result = 31 * result + lineNumber;
	// return result;
	// }

	public static void main(String[] args) {
		Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
		m.put(new PhoneNumber(707, 867, 5309), "Jenny");
		System.out.println(m.get(new PhoneNumber(707, 867, 5309)));
	}
}

通过equals方法比较,两个实例在逻辑上是相等的。

但由于没有覆盖hashCode方法,两个实例返回的hashCode是不同的。

在散列表中,如果散列码不匹配,就不必检查两个实例是否相等。

如果随便提供这样的一个hashCode方法:

public int hashCode(){
    return 42;
}

这样会让散列表失去优势,退化为链表。

最好的hashCode应该是<不同的对象产生不同的散列码>。

即,散列函数把集合中不同的实例均匀地分布到所有可能的散列值上。

下面是一种简单的思路(也就是上面例子中注释的部分):

  1. 把一个非零常数值放在result变量中。
  2. 针对每一个关键的field(假设变量名为f)计算int类型的散列码,不同类型有不同的计算方式。

    boolean:f?1:0

    byte,short,char:(int)f

    long:(int)(f^(f>>>32))

    float:Float.floatToIntBits(f)

    double:Double.doubleToIntBits(f)

    引用:递归调用hashCode

    数组:每个元素作为一个field遵循上述规则

  3. 对计算出的散列码值c进行:result = result*31+c;
  4. 重复测试。

如果一个类是不可变的,而且计算散列值的开销比较大,我们可以试着将散列值缓存。

或者我们也可以试试延迟初始化,在hashCode第一次被调用时进行初始化:

private volatile int hashCode; // (See Item 71)

@Override public int 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;
}

Effective Item 6 - 覆盖equals方法时不要忘记hashCode方法

时间: 2024-10-13 03:53:50

Effective Item 6 - 覆盖equals方法时不要忘记hashCode方法的相关文章

Effective Item 5 - 覆盖equals方法时需要注意

多数情况下,我们不会去覆盖equals方法. 什么时候不需要覆盖equals? ·类的每个实例本质上是唯一的,我们不需要用特殊的逻辑值来表述,Object提供的equals方法正好是正确的. ·超类已经覆盖了equals,且从超类继承过来的行为对于子类也是合适的. ·当确定该类的equals方法不会被调用时,比如类是私有的. 如果要问什么时候需要覆盖equals? 答案正好和之前的问题相反. 即,类需要一个自己特有的逻辑相等概念,而且超类提供的equals不满足自己的行为. (PS:对于枚举而言

为什么重写equals时必须重写hashCode方法?(转发+整理)

为什么重写equals时必须重写hashCode方法? 原文地址:http://www.cnblogs.com/shenliang123/archive/2012/04/16/2452206.html 首先我们先来看下String类的源码:可以发现String是重写了Object类的equals方法的,并且也重写了hashcode方法 public boolean equals(Object anObject) { if (this == anObject) { return true; } i

JAVA中重写equals()方法为什么要重写hashcode()方法?

object对象中的 public boolean equals(Object obj),对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true:注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码.如下:(1)当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true (2)当obj1.ha

为什么重写equals()方法就必须重写hashCode()方法

hashCode()和equals()保持一致,如果equals方法返回true,那么两个对象的hasCode()返回值必须一样.如果equals方法返回false,hashcode可以不一样,但是这样不利于哈希表的性能,一般我们也不要这样做.重写equals()方法就必须重写hashCode()方法的原因也就显而易见了. 假设两个对象,重写了其equals方法,其相等条件是属性相等,就返回true.如果不重写hashcode方法,其返回的依然是两个对象的内存地址值,必然不相等.这就出现了equ

Effective Java - 注意覆盖equals

平时很难遇到需要覆盖equals的情况. 什么时候不需要覆盖equals? 类的每个实例本质上是唯一的,我们不需要用特殊的逻辑值来表述,Object提供的equals方法正好是正确的. 超类已经覆盖了equals,且从超类继承过来的行为对于子类也是合适的. 当确定该类的equals方法不会被调用时,比如类是私有的. 如果要问什么时候需要覆盖equals? 答案正好和之前的问题相反. 即,类需要一个自己特有的逻辑相等概念,而且超类提供的equals不满足自己的行为. (PS:对于枚举而言,逻辑相等

为什么重写equals时必须重写hashCode方法?

原文地址:http://www.cnblogs.com/shenliang123/archive/2012/04/16/2452206.html public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = count; if (n =

java中为什么重写equals时必须重写hashCode方法?

在上一篇博文Java中equals和==的区别中介绍了Object类的equals方法,并且也介绍了我们可在重写equals方法,本章我们来说一下为什么重写equals方法的时候也要重写hashCode方法. 先让我们来看看Object类源码 /** * Returns a hash code value for the object. This method is * supported for the benefit of hash tables such as those provided

JAVA中重写equals()方法为什么要重写hashcode()方法说明

重写hashCode()时最重要的原因就是:无论何时,对同一个对象调用hashCode()都应该生成同样的值.如果在将一个对象用put()方法添 加进HashMap时产生一个hashCode()值,而用get()取出时却产生了另外一个 hashCode()值,那么就无法重新取得该对象了.所以,如果你的hashCode()方法依赖于对象中易变的数据,那用户就要小心了,因为此数据发 生变化时,hashCode()就会产生一个不同的hash码,相当于产生了一个不同的"键". Object的h

为什么重写equals方法需同时重写hashCode方法?

举个小例子来看看,如果重写了equals而不重写hashcode会发生什么样的问题: import java.util.HashMap; public class MyTest { private static class Person { int idCard; String name; public Person(int idCard, String name) { this.idCard = idCard; this.name = name; } @Override public bool