Set接口
1. 概述
Set作为Collection的子接口,没有添加额外的方法,它存储无序的不可重复的数据,在开发中使用比较少,后面会看Map的源码,会了Map,Set就会了。所以Set的重点是理解它的无序性和不可重复性。
为了保证无序性和不可重复性,我们要求添加到Set中的类要重写equals()和hashCode().重写的equals()和hashCode()要保证一致性:相等的对象(equals)必须具有相同的hash值(equals), 这样就可以使我们规定的相等的元素不会重复插入到Set中。
2. Set接口下常用的实现类
-
hashSet: 作为Set接口的主要实现类;是一个线程不安全的;它可以存储 null。底层使用数组+链表实现的。
-
LinkedHashSet: 是hashSet的子类;遍历内部数据时,可以按照添加的顺序去遍历。
-
TreeSet: 可以按照添加元素指定的属性排序,所以要求放入同一个treeSet的对象必须是同一个类new出来的。
3. 添加元素的过程
以hashSet为例。当我们向hashSet中添加元素a时,首先先调用a元素所在类的hashCode()方法,计算出元素a的hash值,根据这个hash值,通过某种算法来确定该元素在hashSet底层数组中的一个存放位置。不同的hashCode通过这种算法可能会被放在同一个位置;如果这个位置上没有其他元素,则直接添加进去,若这个位置上有元素b(或以链表形式的多个元素),则比较a与该链表上所有元素的hash值;如果没有与a相等的hash值的元素,则添加进去,若有相同的hash值的元素,则调用a所在类的equals()方法;如果equals()返回true,则添加失败,返回False,则添加成功。
在jdk8中,添加到链表采用的是尾插法,而jdk7采用的是头插法。
var set = new HashSet();
set.add(new Person("tom",12));
set.add(new Person("tom",12));
Iterator it = set.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
在没有重写Person类的hashCode()方法时,它调用的时Object的hashCode方法,这个方法是native,底层是C语言,我们不可见,它返回的是对象的存储地址。
4. LinkedHashSet
是hashSet的子类, 它也是无序,不可重复的。只不过它在添加数据时,还维护了两个变量,记录这个数据的前一个和后一个数据,使得它在遍历的时候能够按添加的顺序来遍历。
优点:对于频繁的遍历操作,LinkedHashSet效率高于hashSet。
5. TreeSet
5.1 概述
TreeSet的底层是红黑二叉树。TreeSet创造出来就是为了”按照添加元素指定的属性排序“这个功能,所以放入同一个treeSet的对象必须是同一个类new出来的。有两种排序的方式,自然排序(实现Comparable接口)和定制排序(Comparator)。
5.2 自然排序
如果我们使用自然排序的话,我们就要实现Comparable接口,也就是要实现compareTo()。 在自然排序中,比较两个对象相等的标准为compareTo()返回0,而不是equals()
下面是一个例子:
public class Person implements Comparable {
private String name;
private int age;
............
//按照name从大到小排序,age作为二级比较,从小到大排
@Override
public int compareTo(Object o) {
if (o instanceof Person){
Person person = (Person)o;
int compare = -this.name.compareTo(person.name);
if(compare != 0){
return compare;
}
else {
return Integer.compare(this.age, person.age);
}
}
else{
throw new RuntimeException("输入类型不匹配");
}
}
}
5.3 定制排序
我们创建一个Comparator对象,在这个对象里重写compare()方法,之后在声明TreeSet的时候,把这个对象作为参数传入。
在定制排序中,比较两个对象相等的标准为compare返回0,而不是equals()。
Comparator com = new Comparator() {
//只考虑年龄,按小到大排序
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Person && o2 instanceof Person){
Person p1 = (Person) o1;
Person p2 = (Person) o2;
return Integer.compare(p1.getAge(),p2.getAge());
}else {
throw new RuntimeException("输入的数据类型匹配");
}
}
};
TreeSet set = new TreeSet(com);
set.add(new Person("luca",20));
set.add(new Person("lucb",20));