Set集合的特点:元素是无序的,并且无重复元素。Set有两个常用的具体集合子类:HashSet和TreeSet
HashSet:底层数据结构是哈希表,存入取出顺序不一致。
TreeSet:底层是二叉树。可以对存储的元素进行排序。
对于Set集合需要注意的一个问题就是元素不能重复,所以,搞清楚每个集合子类是怎样保证自己元素的无重复性是一个很关键的知识点。
HashSet是如何保证自己元素不重复的呢?同样对于字符串类型HashSet<String>,进行存取的时候是不需要任何操作就可以保证其唯一性,对于自己所定义的类,比如Person,那么HashSet<Person>,如果不加额外处理是达不到唯一性的效果的。如何处理呢?Person类从Object那里除了继承equals的方法外,还会继承hashCode()这个方法。同样默认的hashCode()方法并不能表现出我们所预期的效果,所以我们需要覆盖这个方法。在覆盖这个方法之前,我们先说明下HashSet保证元素唯一性的原理:首先判断元素的哈希值是否相同,即hashCode()的返回值,如果不同,那么这两个元素就被视为不同;如果相同,再调用元素的equals方法进行比较,若不同,则元素视为不同;若相同,则元素视为相同。通过这种比较判断两个元素是否相同。
假设Person有两个属性字段:name,age;
@Override
public int hashCode() {
return this.name.hashCode() + age * 11;
}
通过覆盖,hashCode()方法的表现就是我们所期待的表现。与此同时,还需要覆盖equals方法,和Java List集合操作中相同。
TreeSet是如果保证自己元素的唯一性呢?同样的对于字符串类型TreeSet<String>,进行存取的时候是不需要任何操作就可以保证其唯一性,对于自己所定义的类,比如Person,那么TreeSet<Person>,如果不加额外处理,在向TreeSet<Person>集合中添加超过两个Person对象时就会出现编译时错误。因为TreeSet集合要求存入的元素实现Comparable接口,实现该接口的compareTo()方法。
假设Person有两个属性字段:name,age;
class People implements Comparable<People>{
@Override
public int compareTo(People o) {
int result = this.getName().compareTo(o.getName());
if (result == 0) {
return this.getAge() - o.getAge();
}
return result;
}
}
上面的这种覆盖是按Person的name属性进行排序的,如果name排序相同,则按age排序。TreeSet就是根据compareTo返回的值进行判断的,如果返回是0,那么这两个元素就被视为相同的元素。另外两种结果分别返回正数和负数,根据需求进行升降排序。TreeSet仅通过compareTo方法就能保证其元素的唯一性。
这时,会有一个额外的问题,如果Person不是自己写的,而是来自第三方类,并且还没实现Comparable接口,同时需求中需要对其进行排序。这时候究竟需要用到另一个接口:Comparator。通过额外创建一个类来实现这个接口,并且将其传给TreeSet的构造函数中。
class Cmp implements Comparator<People> {
@Override
public int compare(People o1, People o2) {
int result = o1.getName().compareTo(o2.getName());
if (result == 0) {
return o1.getAge() - o2.getAge();
}
return result;
}
}
TreeSet treeSet = new TreeSet(new Cmp());
通过这种方法就可以解决上述中遇到的问题。