有关在HashSet中hashcode冲突的问题
为什么存放在HashSet里面的对象,如果重写了equals()方法一定要写重写hashcode()方法,也就是说为什么要保证equals()方法比较相等的对象,其hashcode()方法返回值也要一样才可以。
首先,我给出一个例子大家看看,写一个Person类,只是覆盖了equals()方法。
class Person{private String name;private int age;public Person(int age, String name) {super();this.age = age;this.name = name;}public boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Person other = (Person) obj;if (age != other.age)return false;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;}@Overridepublic String toString() {return this.name + " :" + this.age;}}
public class HashSetResearch {public static void main(String[] args) { Set<Person> s = new HashSet<Person>();Person p1 = new Person(22,"zhongyao");Person p2 = new Person(22,"zhongyao");s.add(p1);s.add(p2);for(Person temp : s){System.out.println(temp);}}}程序运行结果为:zhongyao :22zhongyao :22
/** * Adds the specified element to this set if it is not already present. * More formally, adds the specified element <tt>e</tt> to this set if * this set contains no element <tt>e2</tt> such that * <tt>(e==null ? e2==null : e.equals(e2))</tt>. * If this set already contains the element, the call leaves the set * unchanged and returns <tt>false</tt>. * * @param e element to be added to this set * @return <tt>true</tt> if this set did not already contain the specified * element */ public boolean add(E e) {return map.put(e, PRESENT)==null; }
public boolean add(E e) {return map.put(e, PRESENT)==null;}这个HashSet源代码中的add方法。map是HashSet实例中的一个成员变量:private transient HashMap<E,Object> map;PRESENT也是一个成员变量,不过比较特别:// Dummy value to associate with an Object in the backing Mapprivate static final Object PRESENT = new Object(); 这个只是一个”傀儡值”,后面你会看到,在放入到map中是会作为value。既然它调用了HashMap的put(K k, V v)方法,我们就去看看这个方法。这个代码不是很长,认真看还是很好懂的。为了方便,我还是把我的个人理解的注释放到代码之中了 public V put(K key, V value) { if (key == null) return putForNullKey(value);//这个就是键值为空,不用多说了 //获取你放入的key对象的hashcode,看上面就知道这个key指向的就是你想要 //插入的对象,当然在HashMap里做了进一步的处理,事实上就是一些逻辑运算 //有兴趣的可以自己查看 int hash = hash(key.hashCode()); //这个i很有用处,从下面的代码可以看出它是定位你要插入的对象放入到table中的 //位置的,这个table是一个Entry类的数组,是个成员变量,后面会给大家看这个类的 //源代码。从indexFor()的源代码来看只有一行,简单的说就是i=hash&(talbe.length-1) //有点hash函数的味道。 int i = indexFor(hash, table.length); //这个for循环就是为你要插入的对象找到一个更精确的位置,可能在table中i的地方已经有 //人了,那么就要找下一个,如果大家有比较好的数据结构的功底的话应该比较容易理解, //这里处理hash冲突的方法就是在表中具有相同hashcode那一项做链表处理(链地址法) //这些仅仅从for语句的括弧中的三句就可以看的出来 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //以下的这个条件满足,你要插入的对象才不会被插入 //首先第一个就是hashcode,这个不难理解,记得运算是可逆的 //((k = e.key) == key这个的意思是你要插入的对象和当前遍历到的e指向同一个对 //像,当然不能被加入。后面这个key.equals(k)就不多说了。 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this);// do nothing //以上是存在相同时做出新值代替旧值 return oldValue; } } modCount++; //通过上面的for循环找到一个位置了,那么就可以在该方法里直接加如就可以了,这个 //就交给你们去查看了 addEntry(hash, key, value, i); return null;}
import java.util.*;//类A的equals方法总是返回true,但没有重写其hashCode()方法class A{ public boolean equals(Object obj){ return true; }}//类B的hashCode()方法总是返回1,但没有重写其equals()方法class B{ public int hashCode(){ return 1; }}//类C的hashCode()方法总是返回2,但没有重写其equals()方法class C{ public int hashCode() { return 2; } public boolean equals(Object obj) { return true; }}public class TestHashSet{ public static void main(String[] args){ HashSet books = new HashSet(); //分别向books集合中添加2个A对象,2个B对象,2个C对象 books.add(new A()); books.add(new A()); books.add(new B()); books.add(new B()); books.add(new C()); books.add(new C()); System.out.println(books); }}