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

我所要讲解的问题是,为什么存放在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 :22
zhongyao :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 Map
private 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的唯一性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值