当向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; }