Effective Java读书笔记-覆盖equals时总要覆盖hashCode
来源:互联网 发布:2012nba总决赛数据 编辑:程序博客网 时间:2024/06/05 19:46
hashCode,就是哈希值,可以理解为一个对象的标识(好的hash,能确保不同的对象有不同的hash值),Object含有hashCode方法,用来返回对象的hash值。hashCode方法多用在基于散列值的集合类,比如HashMap、HashSet和Hashtable。
下面是hashCode的约束规范,
在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,那么,对该对象调用hashCode方法多次,它必须返回同一个整数。在同一个应用多次执行过程中,这个整数可以不同。
如果两个对象根据equlas方法是相等的,那么调用这两个对象的hashCode方法必须产生同样的整数结果。
如果两个对象根据equals方法是不相等的。那么调用这两个对象的hashCode方法,不要求必须产生不同的整数结果。
如果你重写了类的equals方法,那么必须也重写hashCode方法。否则,就违反了上述的规范。这是因为,两个在逻辑上相等的对象(调用equals相等),必须拥有相同的hashCode,但是根据Object的hashCode,它们仅仅是两个对象,没有共同的地方。所以违背了规范2. 此时,我们就要重写hashCode方法。
实例代码1.
public class Point { private int x; private int y; public Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object obj) { if (!(obj instanceof Point)) return false; Point p = (Point) obj; return p.x == x && p.y == y; } public static void main(String[] args) { Point p1 = new Point(2, 5); Point p2 = new Point(2, 5); System.out.println("p1 equals p2? " + p1.equals(p2)); System.out.println(p1.hashCode()); System.err.println(p2.hashCode()); }}
在上面的例子上,我们重写了equals方法,而且能成功判断p1和p2是相等的,但是没有重写hashCode方法,所以我们调用p1.hashCode和p2.hashCode返回的值是不一样的。
那么,hashCode方法应该是怎么样的呢?编写一个合法的hashCode并不难,比如,
public int hashCode() { return 41; }
由于hashCode规范,并没有要求不同的对象必须有不同的hashCode,所以我们可以给每个对象都返回一个相同的值。虽然这样,并没有违背hashCode的规范。但是在一些散列值存储中(HashSet、HashMap以及HashTable),却带来了灾难。
或许,你并不太了解散列值存储,我们以HashMap为例,HashMap提供了键值对(key-value)的存储,使用范例如下,
Point p1 = new Point(2, 5); Point p2 = new Point(2, 5); HashMap<Point, String> hm = new HashMap<Point, String>(); hm.put(p1, "p1"); hm.put(p2, "p2"); System.out.println(hm.get(p1)); System.out.println(hm.get(p2));
那么,hashCode对于HashMap的作用是什么呢?
我们知道,在HashMap中,不允许两个存在两个相同的对象,那么如何判断两个对象是否相等呢?你或许会说,肯定是调用equals,是的,调用equals没有问题,但是,如果HashMap含有数万条数据,对每个对象都调用equals方法,效率肯定是一个问题。
此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了,说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。下面这段代码是java.util.HashMap的中put方法的具体实现:
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null;}
put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中。从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。
注意,HashMap在插入的时候,判断的是key的值是否相同。
问题来了,如果我们没有重写hashCode方法,那么即使对于两个相同的对象,hashCode的结果也是不一样的(例子1),那么往HashMap中插入数据的时候,就会重复插入(注意,此时的Point并没有实现hashCode方法),
Point p1 = new Point(2, 5); Point p2 = new Point(2, 5); HashMap<Point, String> hm = new HashMap<Point, String>(); hm.put(p1, "p1"); hm.put(p2, "p2"); System.out.println("HashMap size: " + hm.size()); System.out.println(hm.get(p1)); System.out.println(hm.get(p2));
运行程序,我们可以发现,HashMap的大小是2. p1和p2都可以从表中取出。
如果一个类是非可变的,并且计算hashCode的代价比较大,那么应该考虑把hashCode缓存在对象内部,而不是每次都重新计算,如果对于该类的大多数对象都被用于散列键,那么可以在实例被创建的时候就计算hashCode。否则的话,可以选择迟缓初始化hashCode,一直到hashCode第一次使用才初始化。
对于前者,代码可以如下,
private int hashCode; private int x; private int y; public Point(int x, int y) { this.x = x; this.y = y; /** * 在此处初始化hashCode */ } @Override public int hashCode() { return hashCode(); }
在对象初始化的时候,就计算hashCode,然后在hashCode()方法中,直接返回hashCode。
对于后者,代码可以写成这样,
private int hashCode = -1; private int x; private int y; public Point(int x, int y) { this.x = x; this.y = y; } @Override public int hashCode() { if (hashCode() == -1) { /** *此处计算hashCode */ } return hashCode(); }
这样就可以保证hashcode只计算一次,防止多次调用hashcode带来的大量的计算。
- Effective Java读书笔记-覆盖equals时总要覆盖hashCode
- Effective Java (9) 覆盖equals时总要覆盖hashCode
- Effective Java 第九条:覆盖equals时总要覆盖hashCode
- effective java(9) 之覆盖equals时总要覆盖hashCode
- Effective Java(覆盖equals时总要覆盖hashcode方法、始终要覆盖toString)
- java覆盖equals()方法时总要覆盖hashCode()
- 《Effective Java》读书笔记08--覆盖hashCode
- 覆盖equals时总要覆盖hashCode
- 覆盖equals时总要覆盖hashCode
- 覆盖equals时总要覆盖hashCode。
- 《Effective java》读书记录-第9条-覆盖equals时总要覆盖hashCode
- Effective Java 对于所用对象都通用的方法 9.覆盖equals时总要覆盖hashCode
- 第九条:覆盖equals时总要覆盖hashCode
- Effective Java Item9-在覆盖equals方法的同时覆盖hashCode
- Effective Java Item9-在覆盖equals方法的同时覆盖hashCode
- Effective Java读书笔记-覆盖equals时遵守的通用约定
- Java 覆盖equals和hashCode方法
- 覆盖 equals 方法时应同时覆盖 hashCode 。(java)
- 招聘会小试牛刀
- 数据结构与算法
- for中的i++与++i的差别
- 解决本地tomcat部署项目乱码问题
- Euclid(欧几里得)算法
- Effective Java读书笔记-覆盖equals时总要覆盖hashCode
- babyos2(5)——分页
- OpenCV学习第五篇:图像操作
- HRBUST 1547
- Idea自带的文件过滤功能,功能类似gitignore不过只能过滤文件夹
- LSH系列二:p-稳定E2LSH
- T
- 如何编写测试用例(APP)
- 关于链表的C语言实现(中级)