public class Person {
int id;
String name;
//带参构造器
public Person(int id, String name) {
this.id = id;
this.name = name;
}
//空参构造器
public Person() {
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
//重写equals和hashCode方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (id != person.id) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
}
public class CollectionTest {
@Test
public void test3(){
HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");
set.add(p1);
set.add(p2);
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='AA'}]
p1.name = "CC";
set.remove(p1);
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
set.add(new Person(1001,"CC"));
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
set.add(new Person(1001,"AA"));
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
}
}
HashSet底层是一个数组,我们把Person的对象p1、p2存入该数组(p1、p2通过各自属性的哈希值和相应算法,确定p1、p2在数组中的位置),p1和p2的哈希值不一样,故他们在数组中的索引位置很可能也不一样。
代码:
p1.name = "CC";
set.remove(p1);//p1用的索引,通过这个索引能找到“1001, CC”这个数据(p1还是指向原来的对象,只不过这个对象中的属性值由1001, AA变为1001, CC了)
System.out.println(set);
set.remove(p1)
时,p1用的索引,通过这个索引能找到1001,"CC"
这个数据(p1还是指向原来的对象,只不过这个对象中的属性值由1001,"AA"
变为1001,"CC"
了),先算一下1001,"CC"
这个对象的哈希值,通过哈希值和相应地算法找出1001,"CC"
在该数组中的位置。而这个位置很大可能不是下图最左边CC的那个位置,因为下图最左边CC的那个索引位置是根据1001,"AA"
的哈希值及其相应地算法找出来的。
通过哈希值和相应地算法找出的1001,"CC"
在该数组中的位置应为下图最右边的索引位置(假设通过1001,"CC"
的哈希值和相应算法算出的1001,"CC"
索引位置在下图最右边)。我们remove p1时,即remove 1001,"CC"
,是在下图最右边位置上寻找并删除1001,"CC"
,但是最右边的位置是空的,此时remove 1001,"CC"
相当于没有remove。
故System.out.println(set);
时输出[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
而接着上述代码再往下:
set.add(new Person(1001,"CC"));
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
此时是拿1001,"CC"
的哈希值和对应算法去算其在数组中的对应的位置(即最右边的位置),同时下图最右边位置空的,不用调用equals
方法判断是否存在重复元素,直接插入即可。
即如下图:
再接着,重新再数组中添加1001,"AA"
set.add(new Person(1001,"AA"));
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
1001,"AA"
可以加入到数组中,虽然1001,"AA"
的索引位置被1001,"CC"
占据着(最左边),但是1001,"AA"
和1001,"CC"
equals方法比较时,二者不相同,故1001,"AA"
也可以添加成功:
总结:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出元素a在HashSet底层数组中的存放位置(即为:索引位置);
接下来判断数组此位置上是否已经有元素:
- 如果此位置上没有其他元素,则元素a添加成功。 —>情况1
- 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
- 如果hash值不相同,则元素a添加成功。—>情况2
- 如果hash值相同,进而需要调用元素a所在类的
equals()
方法(看元素具体的内容):
2.1.equals()
返回true,元素a添加失败;
2.2.equals()
返回false,则元素a添加成功。—>情况3