`

覆盖hashCode方法

    博客分类:
  • java
 
阅读更多

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

 

 

 

 

 

 

分享到:
评论

相关推荐

    hashcode和equals方法

    equals()和hashcode()这两个方法都是从object类中继承过来的。当String 、Math、还有Integer、Double。。。。等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法.

    java二开面试笔试题

    覆盖 hashCode 方法时,必须同时覆盖 equals 方法,以确保对象的哈希码和 equals 方法的一致性。 二、Java 垃圾回收知识点 1. 垃圾回收机制:Java 中的垃圾回收机制是自动的,可以防止内存泄露。垃圾回收器可以...

    java中hashcode()和equals()方法详解

    - 为了避免哈希冲突带来的性能问题,在覆盖`equals()`方法的同时,通常也需要覆盖`hashCode()`方法。 #### 实现示例 以下是一个简单的类`Person`,展示了如何正确覆盖`equals()`和`hashCode()`方法: ```java ...

    J003-JavaSE-RevE.1.1.pdf

    为了保持hashCode方法的一致性,即两个相等对象的哈希码必须相同,也应该覆盖hashCode方法。自定义类可以通过重写这些方法来满足特定的等同性(equality)和哈希码(hash code)规则。 6. I/O流:在Java中,I/O操作...

    学习Object类——为什么要重写equeals和hashcode方法

    此外,Object 类的设计是为了扩展,它提供了一些非 final 方法,如 equals、hashCode、toString、clone 和 finalize,这些方法都有通用的约定,需要在子类中被覆盖(override)。如果不遵守这些约定,依赖这些约定的...

    Java代码优化1

    - **避免空指针异常**:使用"常量.equals(变量)"避免空指针异常,同时覆盖equals方法时,应同时覆盖hashCode方法。 6. **判断奇偶性**: - **正确使用num%2**:检查num是否为偶数时,使用num%2==0,避免因负奇数...

    Java面试宝典Beta5.0.pdf

    散列码用于确定对象在散列表中的索引位置,建议覆盖equals方法时也覆盖hashCode方法。 8. String类不可继承:String类在Java中被声明为final,因此不能被继承。 9. 方法的值传递和引用传递:在Java中,传递参数到...

    java集合知识-map、set等

    记住:如果元素要存储到HashSet集合中,必须覆盖hashCode方法和equals方法。 一般情况下,如果自定义的类会产生很多对象,比如人,学生,书,通常都需要覆盖equals,hashCode方法。 建立对象判断是否相同的依据。...

    java 序列化和重写 hashCode 的原因

    在Java编程语言中,序列化(Serialization)和重写`hashCode`及`equals`方法是两个重要的概念,它们各自有着特定的用途,并且在某些情况下相互关联。下面将详细阐述这两个概念及其应用。 首先,Java序列化是将一个...

    set接口经常用的hashCode和equals方法详解

    为了实现元素的唯一性,`Set`接口要求覆盖`equals`方法,使其能够根据元素的实际内容来进行比较。 - **内容比较**: 在`Set`接口的不同实现中,通常会根据元素的属性或内容来进行比较。例如,在`String`类中,`...

    为什么在重写 equals方法的同时必须重写 hashcode方法

    首先,根据键的 `hashCode` 定位到对应的桶,然后遍历桶中的条目,如果找到一个条目的键与要插入的键 `equals` 并且哈希值相同,那么就会覆盖旧的值,否则会在桶中添加新的条目。如果两个键的 `equals` 返回 true 但...

    Java 覆盖equals时总要覆盖hashcode

    在Java编程中,当我们重写`equals()`方法时,通常也需要重写`hashCode()`方法。这是因为`equals()`和`hashCode()`方法在Java集合框架中扮演着重要的角色,特别是在使用`HashMap`、`HashSet`等基于哈希表的集合类时。...

    hashcode与eqault比较

    // 重写hashCode方法 @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + age; return result; } } ``` 在这个例子中: - `equals`方法检查两个`...

    散列表的原理与Java实现方法详解

    因此,对于自定义类,开发者应覆盖hashCode方法以提供一个更好的散列函数,确保不同的对象产生不同的散列值。 Java中的HashMap类是实现散列表的常见方式,它提供了put、get、remove等操作,这些操作的时间复杂度在...

    java中hashCode方法与equals方法的用法总结

    4. **覆盖两者**:如果只覆盖 `equals()` 不覆盖 `hashCode()`,可能会导致集合行为异常,因为哈希表依赖于两者的一致性。 在具体实现时,可以使用对象属性的哈希码组合来生成自定义的哈希码,例如: ```java @...

    如何正确实现Java中的HashCode共6页.pdf.z

    - 如果类的实例不会与其他类型对象相等(即没有覆盖`equals()`),则无需重写`hashCode()`。 综上所述,正确实现`hashCode()`方法对于优化Java应用程序的性能和正确性至关重要。在设计和实现类时,必须考虑到`...

    java中hashcode()和equals()的详解.docx

    - **一致性**:在覆盖`equals()`方法时,也需要确保同时覆盖`hashCode()`方法,并且两个方法的实现要保持一致,以避免潜在的哈希冲突问题。 - **性能考量**:在设计`hashCode()`方法时,应尽可能选择分布均匀的哈希...

    面试官瞬间就饱了,重写equals函数,需要重写hashCode函数吗?

    例如,两个内容相同但 `hashCode()` 不同的 `String` 对象会被存储在哈希表的不同位置,导致预期的覆盖操作(例如 `put()`)实际上成为两次独立的添加操作。这违反了哈希表的预期行为,降低了效率并可能导致意外的...

Global site tag (gtag.js) - Google Analytics