Set

在集合Collection里面,有无序的Set和有序的List,这里讲解的是Set的实现类HashSetTreeSet

  • 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());
        }
    }
}

结果:

java唯一 long java唯一并且有序编码_java唯一 long


从结果可以看出,顺序不是我们插入时的顺序,上衣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;
    }

执行结果:

java唯一 long java唯一并且有序编码_TreeSet_02


在这里我们就可以看到 当插入时,会先执行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)));
    }
}

结果:

java唯一 long java唯一并且有序编码_TreeSet与HashSet的使用区别_03


我们发现结果是 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());
        }
    }
}

java唯一 long java唯一并且有序编码_HashSet_04

删除也是一样的,他们先比较了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唯一 long java唯一并且有序编码_TreeSet与HashSet的使用区别_05


发现有一个异常,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;
    }
}

java唯一 long java唯一并且有序编码_HashSet_06


这个时候,就插进去了,因为

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里面所写的方式