除List系列集合外,Collection集合还有另一子类Set集合,该系列集合主要包含TreeSet以及HashSet集合,而HashSet又有一个子集合LinkedHashSet,今天就来说一说这三个集合。

首先说Set集合,该集合的特点是其中的元素无序,并且不允许重复,接下来先看看该集合的子集合HashSet。它是Set接口的典型实现类,使用哈希算法存储元素。首先新建一个HashSet并存入元素,观察其特性:

HashSet hs=new HashSet();
        hs.add("hhhhhhhh");
        hs.add("aaaaaa");
        hs.add("aggoawjgaw");
        hs.add("annnn");
        hs.add(22);
        hs.add(new Person("张三",18));
        hs.add("kawkbk");
        hs.add(11);
        hs.add(new Person("张三",18));
        hs.add("sajlaglg");
        System.out.println(hs);

运行结果:

android set 集合 java set集合_android set 集合


在这里可以发现集合中的元素是无序的,而奇怪的是该集合中存在两个相同的person实例,而set集合的特点就是不允许存在重复元素,于是我再新建两个重复的String对象,看看其结果:

String str1=new String("CC");
        hs.add(str1);
        String str2=new String("CC");
        hs.add(str2);

运行结果:

android set 集合 java set集合_链表_02


可以看到,这里CC只出现了一次,说明重复元素被排除了,那么相较于String类,我手写的实体类一定少重写了某个方法。一般来说,比较两个实例是否相等,重写equals()方法即可,但在Person实体类中,我重写了equals()方法,但仍可以存在重复元素,说明HashSet判断元素是否重复的依据并不止调用equals()方法。而在Object类中,便有一个hashCode()方法,而查看String类的源码,发现其中也重写了该方法:

android set 集合 java set集合_重复元素_03


于是在Person类中,我也重写hashCode()方法:

android set 集合 java set集合_实体类_04


发现运行结果不再出现重复元素了。

上面有说到,HashSet使用了哈希算法的存储元素,在HashSet中,每个元素都有一个对应的hashcode,而每个hashcode都在哈希表上有对应的位置,而HashSet判断元素是否重复,实际上是先进行hashcode值的对比,这样有利于提高存储效率,当hashcode相同时,再调用equals()对比这两个相同的元素,若又相等,则判断该元素为重复元素。

为了确认是否为重复元素,直接输出这两个元素的hashCode值:

android set 集合 java set集合_android set 集合_05


由于HashSet中并没有特有的方法,就不再次写了。

接下来看一下HashSet的子集合LinkedHashSet,查看API我们发现,该集合与HashSet并无差异,但其结构略有差异。既然加上上了Linked,说明该集合和链表有关,新建一个LinkedHashSet:

LinkedHashSet lhs=new LinkedHashSet();
        lhs.add("DaaaafD");
        lhs.add("weaawgawg");
        lhs.add("jlpgjlj");
        lhs.add("B5163B");
        System.out.println(lhs);

运行结果:

android set 集合 java set集合_android set 集合_06


发现其中元素顺序是有序的,在这里我们可以理解为,LinkedHashSet本身是无序的,但输出的顺序是按照链表的前后顺序输出。由于链表维护了元素之间的顺序,该集合相较于HashSet,遍历操作会更为效率高,而增删操作则效率会低一些。

在这里值得注意的是,在之前我对HashSet集合add进AA、BB这样的字母时,发现无论怎么输出,输出的元素顺序都是有序的,我使用的是JDK1.8,在经过查阅资料后,发现在JDK1.8中,hash算法有了一些变化,小位数的数字或是字母会出现算法混淆的情况,从而影响结果。

接下来说一下Set集合的另一实现类TreeSet,简单来说,该集合是一个可以实现排序功能的集合,而如何定义排序,分为两种方式。
一是完整排序,即我们将需要添加进TreeSet集合的元素对应的类,实现Comparable接口,并实现compareTo(Object o)方法,在方法中写明我们想要的排序规则即可。

二是定制排序,在我们不方便将实体类实现Comparable接口时,可以新建一个类实现Comparator接口,并实现接口中的compare(Object o1,Object o2)方法,最后将该实现类的实例作为参数传入TreeSet的构造器即可

比如有一个员工Employee类,包含姓名、年龄以及生日三个属性,而生日又是一个类,其中包含年、月、日三个属性:

android set 集合 java set集合_重复元素_07


android set 集合 java set集合_实体类_08

在这里我们定义的排序规则为按照姓名排序,首先实现Comparable接口,并且实现CompareTo()方法:

android set 集合 java set集合_android set 集合_09


CompareTo方法进行比较时,相同则返回0,大于返回1,小于则返回-1。接下来再测试类中新建一个TreeSet,并且新建5个不同的雇员实例:

android set 集合 java set集合_android set 集合_10


运行结果:

android set 集合 java set集合_实体类_11


由于字符串排序有其自己的规则,就不深究了,为了确定其进行了排序,我们将add5个元素的顺序进行多次对调,发现结果都是如此。

接下来我们使用定制排序的方法,对员工的生日按从大到小排序,若年份相同则比较月份,若月份相同则比较日期
这里我们直接使用匿名内部类:

Comparator comparator=new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof Employee && o2 instanceof Employee){
                    Employee e1=(Employee) o1;
                    Employee e2=(Employee) o2;
                    if (e1.getBirthday().getYear().equals(e2.getBirthday().getYear())){
                        if (e1.getBirthday().getMonth().equals(e2.getBirthday().getMonth())){
                            return e1.getBirthday().getDay().compareTo(e2.getBirthday().getDay());
                        }else {
                            return e1.getBirthday().getMonth().compareTo(e2.getBirthday().getMonth());
                        }
                    }else {
                        return e1.getBirthday().getYear().compareTo(e2.getBirthday().getYear());
                    }
                }
                return 0;
            }
        };
        TreeSet ts=new TreeSet(comparator);

运行结果:

android set 集合 java set集合_android set 集合_12