您的位置:首页 > 其它

详述HashSet add()方法存储自定义类型对象执行过程(内容相同的同一自定义类型的不同对象的添加失败)

2019-05-04 21:49 489 查看

之前分析了HashSet中add方法的执行过程,本篇着重分析存储自定义类型的情况从而了解如何使内容相同的同一自定义类型的不同对象添加失败
因为是自定义类型,所以我们先在同一个包中构建一个自定义类和一个测试类

package com.ame;
import java.util.HashSet;

class Student{
String id;
public Student(String id) {
this.id=id;
}
}

public class Test {
public static void main(String[] args) {
HashSet<Student>set=new HashSet<>();
Student stu=new Student("1");
set.add(stu);
set.add(new Student("1"));
set.add(new Student("1"));
System.out.println(set.size());
}
}

输出结果

3

重写了equals方法和HashCode方法后(自定义学生类中添加如下代码,添加时注意缩进)

@Override
public boolean equals(Object obj) {
Student stu=(Student)obj;
return id.equals(stu.id);
}
@Override
public int hashCode() {

return id.hashCode();
}

输出结果

1

修改了equals方法和HashCode方法之前,根据上一篇博客的分析,相同自定义类的不同对象因为地址不同,可以实现存储;修改了equals方法和HashCode方法之后,相同自定义类的不同对象,内容相同,无法实现存储,equals比较的内容发生了变化。
仍然分析不为空的情况

else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

相同内容无法存储则必然实现了下述代码

if (p.hash == hash &&  ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
if (e != null) {						 // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}//end of else
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;

也即满足了条件

p.hash == hash &&  ((k = p.key) == key || (key != null && key.equals(k)))

上一篇博客中已经提到了hash方法

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

根据判断条件的前后顺序,我们可以看出hashCode()方法先执行,equals()后执行;
因为我们在自定义的Student类中重写了hashCode()方法,所以此处执行的是Student类中的hashCode()方法

@Override
public int hashCode() {

return id.hashCode();
}

又因为id是String类型,所以此时调用的实际上是String类中的hashCode()【如果没有在自定义类中重写,则执行Object类中的hashCode()】,因为是String类,内容相同hash相同
比较了hash,然后比较key,key值来自下方代码

public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

上一篇博客中我们已经分析过知道,key最终是tab[]数组中被存储进去的内容,因为add的是对象,两个不同的对象key是不一样的,key不相等,分析逻辑或后面的条件

key != null && key.equals(k)

因为Student类重写了equals方法,所以此处执行的是Student类中的equals方法,又因为是id调用,id是String类型,所以此时是String类型的equals方法,String类中的equals比较内容,所以条件满足,
此时
return oldValue;
返回旧值,数组tab[]添加失败

@Override
public boolean equals(Object obj) {
Student stu=(Student)obj;
return id.equals(stu.id);
}

需要注意的是,重写的equals方法中发生了向下转型

Student stu=(Student)obj;

所以需要使用instanceof关键字,判断对象是否是是Student类创建的,条件之外注意添加返回值

@Override
public boolean equals(Object obj) {
if (obj instanceof Student) {
Student stu = (Student) obj;
return id.equals(stu.id);
}
return false;
}

总结
HashSet中add()在添加自定义类型对象时,如果对象含参,此时调用自定义类型中的相应有参构造方法,然后会先后调用到hashCode()方法和equals()方法,如果变量都是是String类型或者都是基本数据类型的包装类创建的,则可以通过重写自定义类型中的hashCode()方法和equals()方法,分别使用对象的参数值调用,实现HashSet创建的集合中无法存储——>内容相同同一自定义类型不同对象

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐