最近在读effictive java这本书,看到关于java通用方法重写时的某些规则,又想起项目中重写bean的equals方法,仔细一想确实有很多不正确的地方,所幸项目中的那个对象使用频率低没有出现问题。结合看hashmap的源码时发现读取写入方法与equals,hashcode密切相关,为了不秒忘记,在这记录一下。
equals和hashcode本来都只是object的方法,如果不进行重写,那么equals源码实际上是==比较,比较的是两个对象的地址,除非是同一个对象,否则均为false.
例:public boolean equals(Object obj) {
return (this == obj);
}
而我们经常使用的类库,如String、Character等都是对其进行重写后,有自己相等的逻辑,如String即所有字母相同视为equals.
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
hashcode也是如此,object的hashcode返回的也是和对象地址相关的值,而重写时是根据类的字段有很多规则,在此暂时不深究。但hashcode与equals之间有几个约定规则。
1.如果两个对象通过equals方法判定相等,则他们的hashcode必须相等。
2.反之,如果两个对象有相同的hashcode,却并不一定equals。
3.对同一个对象,无论其内部属性如何改变,应该返回相同的hashcode。
一开始我不太理解这些规则,我想我自己在写一个类时即使不这么做你又能耐我何呢?...
知道我在使用集合类库时得不到正确的结果时我懂了,其实这都是为集合类库做准备,因为我们平时一般操作多个对象都是通过集合类库啊。比如想查看下某个对象的Index
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
显然是用equals查找对象,如果没有重写对象的equals方法,就会调用父类object的equals方法,即为==比较,如果new people("zhangshan"),然后再indexOf(new people("zhangshan"))则会返回-1似乎找不到zhangshan这个刚刚加进去的对象。去年刚毕业时面试似乎还被问到这个问题,当时还不服的觉得是混淆视听,其实是对基本方法的理解不够。
在说hashcode,其实是为hash算法而生,诸如hashset,hashmap,hashtable等,其实看他们的源码会理解的十分深刻。
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
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;
}
首先判断是根据key的hashcode生成hash值,然后根据这个值和数组长度得到其数组下表,即其即将要插入的位置。这里其实包含了很多东西,首先hashcode如果为了方便都返回一样的值,那么所有的键值对都将装在一个桶中,hashmap即退化为普通链表。而如果对象的hashcode不满足上面的第一点,即相同对象hashcode却不同则会导致其在桶中的位置不同,即会造成重复的键,已经不符合map的原则。而hashcode由于是由计算产生,尽管算法尽量避免重复,但当然有重复的可能,所以并不能由此说明两对象equals,此时则会在桶中的相同位置即链表中加入数据。对于第三点是显而易见,如果一个对象仅仅是由于属性值发生变化,hashcode就发生变化,那就根本无法确定它作为键时在桶里位置,也就根本找不到对应的值了,都没机会做equals比较了,如下。
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}