//Person类重写了toString,equils和hashcode方法
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 + '\'' +
'}';
}
@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;
}
}
import java.util.HashSet;
public class CollectionTest {
public static void main(String[] args){
HashSet set = new HashSet();
Person p1 = new Person(1,"a");
Person p2 = new Person(2,"b");
set.add(p1);
set.add(p2);//将p1,p2两个对象添加到HashSet的对象set中
p1.name = "c";
set.remove(p1);//将p1的属性由(1,’a‘ ) 改为 (1,’c‘ ),这时remove p1
System.out.println(set);
1.--------------请问此时输出--------------
set.add(new Person(1,"c"));//添加一个属性为 (1,’c‘ ) 的Person类匿名对象
System.out.println(set);
2.--------------请问此时输出--------------
set.add(new Person(1,"a"));//添加一个属性为 (1,’a‘ ) 的Person类匿名对象
System.out.println(set);
3.--------------请问此时输出--------------
}
}
问:1,2,3,三处分别输出了什么
Person类提供了带参构造器,重写了toString
,equils
和hashcode
方法。
题解在底下,看完题目有了想法再看哈。
题解:
|
|
|
|
我们先公布一下正确答案:
1. [Person{id=1, name='c'}, Person{id=2, name='b'}]
2. [Person{id=1, name='c'}, Person{id=2, name='b'}, Person{id=1, name='c'}]
3. [Person{id=1, name='c'}, Person{id=2, name='b'}, Person{id=1, name='a'}, Person{id=1, name='c'}]
先看第一个输出
第一问的主要操作:
p1.name = "c";
set.remove(p1);`
可以看到,将p1的name属性改为"c"后,尝试删除p1,然而输出却依然存在被更改后的p1 (1,’c‘ )。
- 那我们既然成功赋值了,但却没有删除p1,且编译系统也没有报错。为什么呢?
- 原因是因为,最初的时候,p1 (1,’a‘ ),p2 (2,’b‘ ) 通过各自属性算出的哈希值,并以此确定了在数组中存放的位置,这时改变 p1 (1,’a‘ ) 的属性为 (1,’c‘ ),
- 但p1的所在的位置依然是 属性被没有改变之前(1,“a”)的哈希值所指的(左红框部分),
- 这时,我们想删除p1 (1,’c‘ ) ,HashSet类的对象 set 调用
remove
方法删除前,
先算出p1中已被改变的(1,“c”)这个 属性 的哈希值(右紫框部分),
但此时 下图紫框 的位置上并不存在数据,因此remove
删了个寂寞。。。
第二个输出:
第二问的主要操作:
set.add(new Person(1,"c"));
如果你理解了第一个,很容易就明白此处为什么(1,“c”)这个属性可以加入了。
- hashSet的底层算法不会让有相同属性的对象同时存在。
- (1,“c”)这个属性算出的哈希值在数组中所指位置上并不存在数据(上图的紫框部分)
编译器就认为并不存在相同属性,就将(1,“c”)直接加入到了数组中,没有与红框部分冲突。
所以此时set.add(new Person(1,"c"));
是可以添加进去的。
第三个输出:
第二问的主要操作:`
set.add(new Person(1,"a"));
这个地方就又有一点小难度了。
有小伙伴就会问,你这不是已经把(1,“a”)这个属性的哈希值所指的位置(左红框)占了吗,hashSet不会让有相同属性的对象同时存在的啊,怎么还能加的进去(1,“a”)这个属性???
- 我们来理一遍它添加的流程,
- 添加(1,“a”)这个属性(蓝框)之前,hashcode算出(1,“a”)的哈希值所指的位置(左红框)
- 发现该位置上(红框)有值,这时,调用
equils
方法,返回false,表明其属性【(1,a)与(1,c)】并不相等,于是将(1,“a”)以链表形式挂载在下面。(jdk8)
注意:数组中位置冲突元素与已经存在指定索引位置上数据以链表的方式存储
注:
- HashSet是Set接口的实现类,所以也是存储无序的、不可重复的数据 。
- 底层实际上是HashMap,可以说白了,HashSet就是限制了功能的HashMap,所以了解HashMap的实现原理,这个HashSet自然就通了,对于HashSet中保存的对象,主要要正确重写equals方法和hashCode方法,以保证放入Set对象的唯一性,而本文中的Person类正是重写了这两个方法。
|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储有序的、可重复的数据。
|----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
|----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
|----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
|----Set接口:存储无序的、不可重复的数据
|----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历在添加数据的同时,
每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
|----TreeSet:可以照添加对象的指定属性,进行排序。