当向Set集合中插入对象时,如何判别在集合中是否已经存在该对象。如果采用equals方法对元素逐一进行比较,这样的做法较为耗时。可以先判断hashcode值,HashMap中用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去;如果存在该值, 就调用它的equals方法与新元素进行比较,相同则不存,不同则散列到其他位置。这样当数据量大时就减少了equals函数的调用,极大地提高了效率。
1. Equals
Object类:直接比较两个对象的地址
public boolean equals(Object obj) {
return (this == obj);
}
String类:比较对象的内容,Math、Integer、Double等类中的equals函数也是进行内容的比较
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n– != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
2. Hashcode
hashcode对于list集合没有什么意义,但对HashMap、HashSet、HashTable的存取有重要作用。在Java中hashCode方法的主要作用是为了配合基于散列的集合一起正常运行。hashcode的计算是根据对象的属性进行散列的,过多属性参与散列会降低集合的存取效率,太少则容易发生散列冲突, 从而影响hash列表的性能。
Object类:默认情况下是根据存储地址进行映射
public native int hashCode();
String类:
public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31 * h + val[off++];
}
hash = h;
}
return h;
}
3. hashcode和equals函数的关系:
1) 如果两个对象相同(equals比较相等),那么它们的hashCode值一定要相同。
这样就不会导致两个相同的对象因为hashCode值不一样而同时存入HashMap, 而HashMap不允许存放重复元素
2) 如果两个对象的hashCode相同,它们并不一定相同,这里的对象相同指的是用equals方法比较。
3) 在一个Java应用的执行期间,如果一个对象提供给equals做比较的信息没有被修改的话,该对象多次调用hashCode()
方法,该方法必须始终如一返回同一个整数值。
· 在重写equals方法的同时,必须重写hashCode方法
package com.sa.io;
import java.util.HashMap;
class People {
private String name;
private int age;
public People(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
return name.equals(((People)obj).name) && age == ((People)obj).age;
}
}
public class HashCodeTest {
public static void main(String[] args) {
HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
hashMap.put(new People("kelly", 18), 1);
System.out.println(hashMap.get(new People("kelly", 18)));
// 返回值为null, 因为People只重写了equals函数没有重写hashcode函数,
// 默认情况下,hashCode方法是将对象的存储地址进行映射。这里生成了两个对象,存储地址不一样,因此查找不到该对象
}
}
· hashCode函数依赖的字段变化时,hashCode值也发生变化
class People {
private String name;
private int age;
public People(String name, int age) {
this.name = name;
this.age = age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
return name.hashCode() * 27 + age;
}
@Override
public boolean equals(Object obj) {
return name.equals(((People)obj).name) && age == ((People)obj).age;
}
}
public class HashCodeTest {
public static void main(String[] args) {
HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
People people = new People("kelly", 18);
hashMap.put(people, 1);
people.setAge(23);
System.out.println(hashMap.get(people));
// 返回值为null, 因为age是hashcode函数依赖的属性,该值发生变化后hashcode值也改变
// 如果没有重写hashcode函数,则equals返回true, 然而结果却返回1。因为get方法先用==判断对象是否相同,然后才调用equals
}
}
HashMap的get方法
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
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.equals(k))) // hash -> (key) == -> equals
return e.value;
}
return null;
}