Set
在集合Collection里面,有无序的Set和有序的List,这里讲解的是Set的实现类HashSet与TreeSet
- HashSet:数据结构是哈希表。线程是非同步的,保证元素唯一性的原理:判断元素的hashcode是否相同,如果相同还会判断元素的equals方法
- TreeSet:可以对Set集合中的元素进行排序,底层结构是二叉树,保证数据唯一性的依据:CompareTo的return 0
二者都是元素不能重复的
HashSet
一个用于实验测试的实体类Person
public class Person{
private int age;
private String name;
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
}
往HashSet集合中存自定义的对象
import java.util.HashSet;
import java.util.Iterator;
public class Test {
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add(new Person("lisi1",19));
hs.add(new Person("lisi2",21));
hs.add(new Person("lisi3",20));
hs.add(new Person("lisi4",20));
hs.add(new Person("lisi4",20));
Iterator it = hs.iterator();
while (it.hasNext()){
Person p = (Person)it.next();
System.out.println("name:" + p.getName() + ";age:" + p.getAge());
}
}
}
结果:
从结果可以看出,顺序不是我们插入时的顺序,上衣HashSet时无序的,但是上面却出现了两个相同的元素。不是说元素不能相同吗?
之所以相同,是因为HashSet的插入时,是按照Person的hashcode和equals两个方法来判断的,而hashcode的返回值是我们Person对象的地址,显然,二者的地址是不同的,所以在插入的时候,才出现了两个“相同”的元素,这个时候,我们就需要重写Person的hashcode方法和equals方法了
在内部比较二者是否相等时,会先比较hashcode是否相同,当hashcode返回值相同时,内部才会比较equals方法,因为当hashcode不同时,系统就知道,你这两个值肯定不同嘛,所以就不执行后面的比较了
重写方法后的Person部分
@Override
public int hashCode(){
System.out.println("hashCode和"+ this.name + "---" + this.age +"比较");
return this.name.hashCode() + this.age * 40;//随机一个数,为了尽可能避免4 + 4 = 8和3 + 5 = 8的类似情况出现
}
@Override
public boolean equals(Object obj){
if(!(obj instanceof Person)){
return false;
}
Person p = (Person) obj;
System.out.println("equals和"+ this.name + this.age +"比较");
return p.getName().equals(this.name) && p.getAge() == this.age;
}
执行结果:
在这里我们就可以看到 当插入时,会先执行hashCode方法,当hashCode的返回值不同时,就直接结束,不同时,即再次插入new Person(“lisi4”,20) 时,就执行equals方法了,来做最后的判断,发现还是一样的,所以就没有插进去所以
new Person(“lisi4”,20)
就只有一个了,因为在比较完他们的hashCode后,发现相等,于是又调用了equals方法,发现以我们给定的判断方法,他们是相等的,所以就不存就去了
在集合的内部已经自动的调用了 hashCode 和 equals方法了,不需要我们自己调用
HashSet的删除和判断是否存在的依据
1、判断是否存在
public class Test {
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add(new Person("lisi1",19));
hs.add(new Person("lisi2",21));
hs.add(new Person("lisi3",20));
hs.add(new Person("lisi4",20));
System.out.println("是否包含");
System.out.println(hs.contains(new Person("lisi2",21)));
}
}
结果:
我们发现结果是 true 说明存在,他们先比较了hashCode,发现相等,然后再比较equals,发现也相等,所以返回true,告诉我们里面已经包含该元素了
2、删除
public class Test {
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add(new Person("lisi1",19));
hs.add(new Person("lisi2",21));
hs.add(new Person("lisi3",20));
hs.add(new Person("lisi4",20));
System.out.println("删除");
System.out.println(hs.remove(new Person("lisi2",21)));
Iterator it = hs.iterator();
while (it.hasNext()){
Person p = (Person) it.next();
System.out.println(p.getName() + "--" + p.getAge());
}
}
}
删除也是一样的,他们先比较了hashCode,发现相等,然后再比较equals,发现也相等,所然后就它与它相等的删除,并返回一个true
HashSet的删除和检查是否包含,都是依赖hashCode和equals两个方法(前面已经证明了,添加也是依赖这两个方法)
TreeSet
TreeSet依然是无序的,但是却可以对集合中的元素进行排序,即他们的顺序默认是和我们插入时不一样的,但是我们可以写一个规则规定它的排序方式
在这里我们还是以存储自定义的类举例子
实验实体类还是上面的那个Person
执行方法如下
public class Test {
public static void main(String[] args) {
TreeSet ts = new TreeSet();
ts.add(new Person("lisi1",19));
ts.add(new Person("lisi2",21));
ts.add(new Person("lisi3",20));
ts.add(new Person("lisi4",20));
Iterator it = ts.iterator();
while (it.hasNext()){
Person p = (Person) it.next();
System.out.println(p.getName() + "--" + p.getAge());
}
}
}
结果;
发现有一个异常,java.lang.ClassCastException,类映射异常,这时因为TreeSet能排序,但是这是我们自定义的类,集合内部压根就不知道该怎么排序,所以就给我们报了一个无法比较的异常,所以这个时候,我们的Person类就应该实现一个能实现的接口,让Person能进行自然的排序,这个接口就是Comparatable,
新的实体类如下,继承了Comparable接口,且实现了它的compareTo方法,
public class Person implements Comparable<Person>{
private int age;
private String name;
public int compareTo(Person p){
System.out.println(this.age + "-compareTo-" + p.age);
if(this.age > p.age)
return 1;
else if(this.age == p.age)
return 0;
return -1;
}
public Person(String name, int age){
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
}
这个时候,就插进去了,因为
if(this.age > p.age)
return 1;
else if(this.age == p.age)
return 0;
return -1;
即我们从小到大的顺序规定了排序,所以迭代输出时,按年龄从小到大,因为compareTo规定年龄相等时,就返回0,这个时候内部就判断的认为这两个类是一样的,所以第二个年龄时20的对象就没有添加进去
TreeSet虽然不是按我们添加的顺序排序,但是我们可以在compareTo里面规定我们内部的顺序,如果想要按我们添加的顺序排序时,只需要将compareTo改写成 return 1;就行了,不做任何判断,直接返回1就可以了,这个时候就是按我们添加的顺序排序
TreeSet的底层数据结构是二叉树,保证元素的唯一性的依据是compareTo的返回值是0,底层二叉树的排序方式,就是我们compareTo里面所写的方式