Java中equals方法和hashCode方法
2014-09-13 17:43
330 查看
《Effective Java》第2版的第9条条款要求“覆盖equals方法的类总要覆盖hashCode方法”,如果不这么做,就违反了Object.hashCode() 的通用约定,导致该类的实例无法与基于散列的集合(HashMap, HashSet, Hashtable)一起使用。
约定的内容包括:
1. 应用程序执行期间,只要对象equals方法的比较操作使用的信息没有被修改,前后调用hashCode的到的值应保持不变
2. 两个对象根据equals(Object)方法比较是相等的,则它们分别调用hashCode得到的值也应相等
3. 两个对象根据equals(Object)方法比较不相等,则它们的的hashCode返回的值不要求一定不相等
基于散列的集合通过hashCode方法计算得到的哈希码确定需要存放数据的散列桶,根据上述第3条描述,逻辑上不相等的对象有可能会产生相等的哈希码,所以哈希表有可能冲突,Java中的HashMap通过链地址法(链表)处理冲突。
如果某个类覆盖了equals方法,但没有覆盖hashCode方法,当该类的实例作为HashMap的key时,会出现问题:
为什么会出现这样的结果?因为PhoneNumber类并没有覆盖hashCode方法,所以会导致前后两个new PhoneNumber(707, 102, 888)的哈希码是完全没有关系的两个随机数,因此,后面从map中取数据时,也就不能获取存放之前数据的那个散列桶,所以无法获取希望的返回值“Jenny”;要解决这一问题,还需要重写hashCode方法。
那么怎样编写一个正确且有效的hashCode方法呢?正确性要求逻辑相等的对象,他们的hashCode必须返回相等的值;而有效性则要求不相等的对象尽可能获得不同的返回值。《Effective Java》中介绍了一种常用的方法:首先把某个整形常数值保存在result中;然后使用公式: result = result * 31 + c;c的计算方法如下:如果域的类型为byte, char, short, int,直接返回;如果为boolean,返回1或0;如果为float,返回Float.floatToIntBits(f);如果为long,返回前32位和后32位亦或操作后的返回值;如果为double,返回Double.doubleToIntBits(d)及long类型的操作后的值;对于引用类型,则递归调用下去。对于上述PhoneNumber的类型,其hashCode的定义如下:
注:计算散列码时不要为了性能而忽略某个关键字段,虽然计算过程的效率提高了,但有可能会因此而增大散列冲突的概率,从而影响整体的效率
约定的内容包括:
1. 应用程序执行期间,只要对象equals方法的比较操作使用的信息没有被修改,前后调用hashCode的到的值应保持不变
2. 两个对象根据equals(Object)方法比较是相等的,则它们分别调用hashCode得到的值也应相等
3. 两个对象根据equals(Object)方法比较不相等,则它们的的hashCode返回的值不要求一定不相等
基于散列的集合通过hashCode方法计算得到的哈希码确定需要存放数据的散列桶,根据上述第3条描述,逻辑上不相等的对象有可能会产生相等的哈希码,所以哈希表有可能冲突,Java中的HashMap通过链地址法(链表)处理冲突。
如果某个类覆盖了equals方法,但没有覆盖hashCode方法,当该类的实例作为HashMap的key时,会出现问题:
import java.util.HashMap; import java.util.Map; public class PhoneNumber { private final int areaCode; private final int prefix; private final int lineNumber; public PhoneNumber(int areaCode, int prefix, int lineNumber){ this.areaCode = areaCode; this.prefix = prefix; this.lineNumber = lineNumber; } @Override public boolean equals(Object o){ if(this == o) return true; if(!(o instanceof PhoneNumber)) return false; PhoneNumber pn = (PhoneNumber)o; return this.areaCode == pn.areaCode && this.prefix == pn.prefix && this.lineNumber == pn.lineNumber; } public static void main(String[] args){ Map<PhoneNumber, String> map = new HashMap<PhoneNumber, String>(); map.put(new PhoneNumber(707, 102, 888), "Jenny"); System.out.println(map.get(new PhoneNumber(707, 102, 888))); } }上述代码的执行结果是:null
为什么会出现这样的结果?因为PhoneNumber类并没有覆盖hashCode方法,所以会导致前后两个new PhoneNumber(707, 102, 888)的哈希码是完全没有关系的两个随机数,因此,后面从map中取数据时,也就不能获取存放之前数据的那个散列桶,所以无法获取希望的返回值“Jenny”;要解决这一问题,还需要重写hashCode方法。
那么怎样编写一个正确且有效的hashCode方法呢?正确性要求逻辑相等的对象,他们的hashCode必须返回相等的值;而有效性则要求不相等的对象尽可能获得不同的返回值。《Effective Java》中介绍了一种常用的方法:首先把某个整形常数值保存在result中;然后使用公式: result = result * 31 + c;c的计算方法如下:如果域的类型为byte, char, short, int,直接返回;如果为boolean,返回1或0;如果为float,返回Float.floatToIntBits(f);如果为long,返回前32位和后32位亦或操作后的返回值;如果为double,返回Double.doubleToIntBits(d)及long类型的操作后的值;对于引用类型,则递归调用下去。对于上述PhoneNumber的类型,其hashCode的定义如下:
@Override public int hashCode(){ int result = 17; result = result * 31 + areaCode; result = result * 31 + prefix; result = result * 31 + lineNumber; return result; }添加上述代码后返回:Jenny
注:计算散列码时不要为了性能而忽略某个关键字段,虽然计算过程的效率提高了,但有可能会因此而增大散列冲突的概率,从而影响整体的效率
相关文章推荐
- java.lang.Object的equals()和hashCode()方法小结
- java中的hashcode()和equals()方法详解
- java equals与hashCode 两个重要方法的重写
- JAVA中重写equals()方法为什么要重写hashcode()方法说明
- Java中的hashcode()和equals()方法详解
- Java中的equals方法与hashCode方法解析
- JAVA中的equals()和hashCode()方法(经典)
- java 重写类的equals方法和hashcode方法
- 一种常见的Java编程错误:没有同时定义equals()和hashCode()方法
- java中重写Object类的equals方法和Hashcode方法的注意事项
- 深入java 的equals方法和hashCode方法
- Java软件低级错误: 定义equals方法的同时也要定义hashCode方法
- java对象通用方法之覆盖equals时请遵守通用约定、覆盖equals时总要覆盖hashCode、始终要覆盖toString、考虑实现Comparable接口
- Java中的equals()和hashCode()方法
- Java Collection框架在Collection的生命周期中需要基于不变字段的equals()和hashCode()方法
- Java Class 中hashCode()和equals方法
- JAVA中重写equals()方法为什么要重写hashcode()方法说明
- Java中重写Object类的equals方法和 hashcode方法的注意事项
- Java中重写Object类的equals方法和hashcode方法的注意事项
- java.lang.Object的equals()和hashCode()方法小结