1.Set接口
Set接口和上节讲到的List接口都继承了Collection接口,而Set接口和List接口最大的不同就是List中的元素是可以重复的,而Set中的元素不能重复,所以Set中的元素必须定义equals方法来保证元素的唯一性。除此之外,List接口放入元素是有序的,在使用时可以通过下标来找到对应位置的元素,而Set不保证放入元素的顺序,当然,仅仅是不保证而已,Set也是有维护了元素顺序的实现类的。接下来看一下Set的常见实现类:HashSet、TreeSet、LinkedHashSet。
2.HashSet
HashSet实现Set接口,基于HashMap实现,底层使用HashMap保存数据。HashSet的实现其实很简单,基本就是封装了一下HashMap,看一下几个主要方法的源码:
构造方法:
public HashSet() {
this(new HashMap<E, HashSet<E>>());
}
public HashSet(int capacity) {
this(new HashMap<E, HashSet<E>>(capacity));
}
可以看到使用了HashMap作为容器。
常用方法:
@Override
public boolean add(E object) {
return backingMap.put(object, this) == null;
}
@Override
public boolean isEmpty() {
return backingMap.isEmpty();
}
@Override
public boolean remove(Object object) {
return backingMap.remove(object) != null;
}
@Override
public int size() {
return backingMap.size();
}
//HashSet中的元素作为key保存在HashMap中,所以HashSet的迭代器使用了HashMap的keySet返回所有的key
@Override
public Iterator<E> iterator() {
return backingMap.keySet().iterator();
}
很简单,都是HashMap的基本方法,在理解了HashMap后,就能很容易明白HashSet方法的实现。
@Override
public boolean contains(Object object) {
return backingMap.containsKey(object);
}
这里又提到了contains方法。上一节在讲到ArrayList的时候提过,如果要正确比较ArrayList中的两个元素是否相同,需要重写equals方法。而对于HashSet,除了要重写存入对象的equals方法外,还需要重写hashCode方法,保证HashSet中没有相同的两个对象,这点是和HashMap一样的。
总结一下HashSet的特点:HashSet中不能有相同的元素,可以有一个null元素,存入的元素是无序的。简单的说,其实HashSet就是操作了HashMap的key,并且同时具有Set接口的性质。
3.LinkedHashSet
LinkedHashSet是HashSet的子类,它和HashSet的区别就在于LinkedHashSet的元素严格按照放入顺序排列。LinkedHashSet内部使用LinkedHashMap实现,所以它和HashSet的关系就相当于HashMap和LinkedHashMap的关系。如果你想让取出元素的顺序和插入元素的顺序完全相同,那么就使用LinkedHashSet代替HashSet。
4.TreeSet
上面提到HashSet是用HashMap实现的,其实这里的TreeSet也是用Map接口的另一个实现类TreeMap实现的。TreeMap是一个有序的二叉树。TreeSet实现了SortedSet接口,其特点是会对放入其中的元素进行排序,和LinkedHashSet不同的是,LinkedHashSet是根据元素的放入顺序进行排序,而TreeSet是根据元素的自然顺序进行排序。
例如:
TreeSet<Integer> set = new TreeSet<Integer>();
set.add(3);
set.add(2);
set.add(5);
set.add(3);
set.add(1);
set.add(5);
System.out.println(Arrays.toString(set.toArray()));
//打印出来的结果[1, 2, 3, 5]
不要问我为什么只有4个数,往上翻翻看看Set的性质(敲黑板!!)
同样的,如果往TreeSet中放入String类型的数据,输出时也会按照英文字母的顺序输出,这就是前面说的,TreeSet会将放入的元素按照自然顺序排列。
那么,如果放入的元素是自定义的对象呢?
很简单,之所以TreeSet能对Integer和String类型的数据进行排序,是因为Integer和String都实现Comparable接口并实现了compareTo方法。那么如果我们自定义的对象要放入TreeSet实现排序,也只需要实现Comparable接口并自定义compareTo方法,来实现让TreeSet中的对象按照我们希望的规则排列。
另一种方法,在TreeSet的构造方法中,可以传入一个Comparator
public TreeSet(Comparator<? super E> comparator) {
backingMap = new TreeMap<E, Object>(comparator);
}
可以在这个Comparator中定义比较的规则,来对传入的对象进行排序。
从上面对TreeSet性质的叙述中,我们不难得出另一个结论:TreeSet中不允许放入null元素。
实际开发中,TreeSet的使用频率较低,是因为TreeSet每插入一个数据,就会排一次序,导致性能降低,而一般我们都是放入一堆数据后再一起排序,所以用不到TreeSet。但如果刚好碰到需要进行插入后即时排序的需求,那这时候就可以用上TreeSet了~
5.总结
在Set接口的实现类中,HashSet是一种元素不重复且无序的存储容器,可以存储一个null元素,放入HashSet的对象需要重写equals和hashCode方法以保证存入对象唯一;LinkedHashSet是HashSet的子类,具有HashSet的性质,且它保存了元素放入的顺序;TreeSet可以将存入的元素按照一定的规则排序,对象放入TreeSet前需要实现compareTo方法来定义这个规则,TreeSet中不能存在null元素。
网友评论