除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);
运行结果:
在这里可以发现集合中的元素是无序的,而奇怪的是该集合中存在两个相同的person实例,而set集合的特点就是不允许存在重复元素,于是我再新建两个重复的String对象,看看其结果:
String str1=new String("CC");
hs.add(str1);
String str2=new String("CC");
hs.add(str2);
运行结果:
可以看到,这里CC只出现了一次,说明重复元素被排除了,那么相较于String类,我手写的实体类一定少重写了某个方法。一般来说,比较两个实例是否相等,重写equals()方法即可,但在Person实体类中,我重写了equals()方法,但仍可以存在重复元素,说明HashSet判断元素是否重复的依据并不止调用equals()方法。而在Object类中,便有一个hashCode()方法,而查看String类的源码,发现其中也重写了该方法:
于是在Person类中,我也重写hashCode()方法:
发现运行结果不再出现重复元素了。
上面有说到,HashSet使用了哈希算法的存储元素,在HashSet中,每个元素都有一个对应的hashcode,而每个hashcode都在哈希表上有对应的位置,而HashSet判断元素是否重复,实际上是先进行hashcode值的对比,这样有利于提高存储效率,当hashcode相同时,再调用equals()对比这两个相同的元素,若又相等,则判断该元素为重复元素。
为了确认是否为重复元素,直接输出这两个元素的hashCode值:
由于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);
运行结果:
发现其中元素顺序是有序的,在这里我们可以理解为,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类,包含姓名、年龄以及生日三个属性,而生日又是一个类,其中包含年、月、日三个属性:
在这里我们定义的排序规则为按照姓名排序,首先实现Comparable接口,并且实现CompareTo()方法:
CompareTo方法进行比较时,相同则返回0,大于返回1,小于则返回-1。接下来再测试类中新建一个TreeSet,并且新建5个不同的雇员实例:
运行结果:
由于字符串排序有其自己的规则,就不深究了,为了确定其进行了排序,我们将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);
运行结果: