Java集合之Set

正文

Set的特点?

  • 它与List(关于List可以参考Java集合之List)不同,它是一个不包含重复元素的collection(Set所具有的方法和Collection所具有的方法一致)。

  • 它是无序的,但当向集合中存储元素的顺序与Set内部存储元素的顺序一致时就可能会出现有序的情况,只不过这种情况出现的概率很小。

Set的常用子类?

关于这一部分我们的讲解思路与List相同:先明确各自的特点,知道具体的场景该用哪一种;再分析各自所具有的一些方法。这两者都可以通过查看jdk文档获悉。

HashSet?

HashSet内部的数据结构是哈希表;它是不同步的。

那么哈希表是什么呢?顾名思义,它就是一张使用哈希算法得出的表。通过哈希算法,我们存取元素的过程就变成了这样:假如我们要存储元素A,先根据哈希算法算出A元素的哈希值,再根据该哈希值得出元素A存储到集合中的位置,根据该位置存储即可;假如我们要从集合中取出元素B,还是先根据哈希算法算出B元素的哈希值,再根据该哈希值到集合中指定的位置查找,如果该位置上的元素是B,就直接取出,如果不是,就说明该集合中没有B元素。

由于哈希表的这种特点,所以哈希表中的元素是不能重复的。哈希表确定两个元素是否相同的依据是:先用hashCode()算出两个元素的哈希值,如果二者相等再使用equals()判断两者的内容,如果两者内容也相同,那么哈希表就认为这两个元素是相同的。

由于Object类中有hashCode(),所以Java中的所有对象都有哈希值。并且Object类中的hashCode()被native修饰是底层实现的。但是我们可以自定义该方法的实现。

当两个不同的元素算出的哈希值却相等时,就出现了哈希冲突。这种情况下如何进行存取元素就会视具体的哈希算法而定。

当向HashSet中存储自定义对象并且我们对这些对象是否相等有自己的逻辑时,就可以在对象所属的类中覆盖hashCode()和equals()。假设现在有这样的需求:向HashSet集合中存储Person对象并且如果姓名和年龄相同就视为同一个人。代码如下:


import java.util.HashSet;
import java.util.Iterator;

class Person {
    private String name;
    private int age;

    public Person() {
        super();

    }
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    /**
     * 覆盖hashCode()和equals()实现自己的判断逻辑
     * @return
     */
    @Override
    public int hashCode() {
        return name.hashCode() + age * 27;
    }
    @Override
    public boolean equals(Object obj) {
        if(this == obj)
            return true;
        if(!(obj instanceof Person))
            throw new ClassCastException("类型错误");

        Person p = (Person)obj;
        return this.name.equals(p.name) && this.age == p.age;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String toString(){
        return name+":"+age;
    }
}

class Demo {
    public static void main(String[] args) {

        HashSet hs = new HashSet();
        hs.add(new Person("lisi4",24));
        hs.add(new Person("lisi7",27));
        hs.add(new Person("lisi1",21));
        hs.add(new Person("lisi7",27));


        Iterator it = hs.iterator();
        while(it.hasNext()){
            Person p = (Person)it.next();
            System.out.println(p);
        }
    }
}

我们要注意List接口中的contains()和remove()内部使用的都是equals()进行判断,即是:如果我们需要使用contains()或equals()实现自定义判断逻辑,只需覆盖equals()即可。假设现在有这样的需求:删除ArrayList中重复的Person对象并且当Person对象的姓名和年龄相同就视为同一个人。代码如下:


import java.util.ArrayList;
import java.util.Iterator;

class Person {
    // 此处省略Person类的属性、构造函数、set/get方法、toString(),与上同

    @Override
    public boolean equals(Object obj) {    // 仅覆盖equals()即可
        if(this == obj)
            return true;
        if(!(obj instanceof Person))
            throw new ClassCastException("类型错误");

        Person p = (Person)obj;
        return this.name.equals(p.name) && this.age == p.age;
    }
}

class Demo {
    public static void main(String[] args) {

        ArrayList al = new ArrayList();
        al.add(new Person("lisi1", 21));
        al.add(new Person("lisi2", 22));   // 1
        al.add(new Person("lisi3", 23));
        al.add(new Person("lisi4", 24));
        al.add(new Person("lisi2", 22));
        al.add(new Person("lisi3", 23));

        al = getSingleElement(al);
        System.out.println(al);

        System.out.println(al.remove(new Person("lisi2",22)));   // 与1处的对象是两个对象,但由于我们覆盖了equals()并实现了自己的判断逻辑,所以返回true并且删除成功
	System.out.println(al);
    }

    private static ArrayList getSingleElement(ArrayList al) {
        ArrayList temp = new ArrayList();

        Iterator it = al.iterator();

        while(it.hasNext()){
            Object obj = it.next();

            if(!temp.contains(obj)){
                temp.add(obj);
            }
        }
        return temp;
    }
}

所以我们可以总结:如果涉及到判断两个对象是否相等,最好hashCode()和equals()都进行覆盖,这样对List和HashSet都适用。

如果我们希望集合在唯一时还能保证有序性,我们就可以使用LinkedHashSet。它内部是由链表和哈希表共同实现的。

TreeSet?

TreeSet的内部的数据结构是二叉树;它使用元素的自然顺序对元素进行排序;它是不同步的。

TreeSet判断元素是否唯一的根据是:判断比较方法的返回结果,如果是0,就说明是同一个元素。

我们需要注意:TreeSet对元素进行排序有两种方式,第一种就是让需要排序的类实现Comparable接口并且覆盖compareTo()。假设现在有这样的需求:以Person对象的年龄进行从小到大的排序。代码如下:


// 此处省略Person类的属性、构造函数、set/get方法、toString(),与上同

import java.util.Iterator;
import java.util.TreeSet;

class Person implements Comparable {   // 实现Comparable接口 
    @Override
    public boolean equals(Object obj) {    // 仅覆盖equals()即可
        if(this == obj)
            return true;
        if(!(obj instanceof Person))
            throw new ClassCastException("类型错误");

        Person p = (Person)obj;
        return this.name.equals(p.name) && this.age == p.age;
    }

    @Override  
    public int compareTo(Object o) {   // 覆盖compareTo()并实现自己的判断逻辑
        Person p = (Person)o;

        int  temp = this.age - p.age;
        return temp == 0 ? this.name.compareTo(p.name) : temp;
    }
}

class Demo {
    public static void main(String[] args) {

        TreeSet ts = new TreeSet();

        ts.add(new Person("zhangsan",28));
        ts.add(new Person("lisi",21));
        ts.add(new Person("zhouqi",29));
        ts.add(new Person("zhaoliu",25));
        ts.add(new Person("wangu",24));

        Iterator it = ts.iterator();

        while(it.hasNext()){
            Person p = (Person)it.next();

            System.out.println(p.getName() + ":" + p.getAge());
        }
    }
}

有很多类都实现了Comparable接口,意味着这些类都自定义了排序方式,这种排序方式被称为类的自然排序,那么当我们不想要按照这种自然排序方式进行排序时,就可以使用第二种方法:定义一个类(比较器)让其实现Comparator接口并且覆盖compare(),并将该类对象作为参数传递给TreeSet集合的构造函数。代码如下:


import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

class Person {
// 此处省略Person类的属性、构造函数、set/get方法、toString(),与上同

    @Override
    public int hashCode() {
        return name.hashCode() + age * 27;
    }
    @Override
    public boolean equals(Object obj) {
        if(this == obj)
            return true;
        if(!(obj instanceof Person))
            throw new ClassCastException("类型错误");

        Person p = (Person)obj;
        return this.name.equals(p.name) && this.age == p.age;
    }
}

/**
 * 一个根据Person类的name进行排序的比较器
 */
class ComparatorByName implements Comparator {

    @Override
    public int compare(Object o1, Object o2) {

        Person p1 = (Person)o1;
        Person p2 = (Person)o2;

        int temp = p1.getName().compareTo(p2.getName());

        return temp == 0 ? p1.getAge() - p2.getAge() : temp;
    }

    public static void main(String[] args) {
        TreeSet ts = new TreeSet(new ComparatorByName());


        ts.add(new Person("zhangsan",28));
        ts.add(new Person("lisi",21));
        ts.add(new Person("zhouqi",29));
        ts.add(new Person("zhaoliu",25));
        ts.add(new Person("wangu",24));

        Iterator it = ts.iterator();

        while(it.hasNext()){
            Person p = (Person)it.next();

            System.out.println(p.getName() + ":" + p.getAge());
        }
    }
}

由于HashSet可以实现任意方式的排序,那我们也可以实现HashSet的有序,代码见下:


import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

class Person {
// 此处省略Person类的属性、构造函数、set/get方法、toString(),与上同

    @Override
    public int hashCode() {
        return name.hashCode() + age * 27;
    }
    @Override
    public boolean equals(Object obj) {
        if(this == obj)
            return true;
        if(!(obj instanceof Person))
            throw new ClassCastException("类型错误");

        Person p = (Person)obj;
        return this.name.equals(p.name) && this.age == p.age;
    }
}

/**
 * 一个实现HashSet有序的比较器
 */
class ComparatorByName implements Comparator {

    @Override
    public int compare(Object o1, Object o2) {

        Person p1 = (Person)o1;
        Person p2 = (Person)o2;

        return 1; 
    }

    public static void main(String[] args) {
        TreeSet ts = new TreeSet(new ComparatorByName());


        ts.add(new Person("zhangsan",28));
        ts.add(new Person("lisi",21));
        ts.add(new Person("zhouqi",29));
        ts.add(new Person("zhaoliu",25));
        ts.add(new Person("wangu",24));

        Iterator it = ts.iterator();

        while(it.hasNext()){
            Person p = (Person)it.next();

            System.out.println(p.getName() + ":" + p.getAge());
        }
    }
}

posted @ 2020-03-20 17:37  samsaraaa  阅读(149)  评论(0编辑  收藏  举报