有关在HashSet中的对象的重写hashcode()和equals()

xiaoxiao2026-04-11  5

我所要讲解的问题是,为什么存放在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; } @Override public 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 在HashSet中,不可以装重复的对象,这个是大家都知道的,具体描述可以见jdk中的HashSet类的相关javadoc描述 /** * 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; } e==null ? e2==null : e.equals(e2), 这说明在HashSet里不能拥有和e相同的element的,相同的条件是同时为null或者equals()方法返回true。这时候你可能会问,那为什么上面的p2会被加入到s中呢? 在你调用HashSet的时候发生了很多事情,其中就有用到对象的hashcode()方法,我将把整个流程的调用过程详细列出。 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;} 附HashSet.Entry的源代码: static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; final int hash; /** * Creates new entry. */ Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } public final int hashCode() { return (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); } public final String toString() { return getKey() + "=" + getValue(); } /** * This method is invoked whenever the value in an entry is * overwritten by an invocation of put(k,v) for a key k that's already * in the HashMap. */ void recordAccess(HashMap<K,V> m) { } /** * This method is invoked whenever the entry is * removed from the table. */ void recordRemoval(HashMap<K,V> m) { } } 这个在所有的set中几乎是差不多的,大家可以自己看看。提一点,在TreeSet中,还额外要求compareTo()返回一样,即equals()返回true时,compareTo()要返回0 问题: 在加入到hashset之后,修改对象的状态和其它的一样,那么也是可以的,不会自动断裂,这个就是我想要了解了,这个破坏了set的唯一性。
转载请注明原文地址: https://www.6miu.com/read-5047186.html

最新回复(0)