一、Set集合的使用注意事项
(1)set接口下有三个实现类HashSet、TreeSet、LinkedHashSet。常用前两个。
(2)要知道set集合中的元素是无序的(是指存入的顺序跟取出的顺序是不一致的),且不可以重复。
(3)但TreeSet又说是有序的,但是这个有序不是跟上面set无序相反概念,这个有序是指,按照存入元素本身的自然顺序自动进行排序,最后输出一列有序元素。
二、HashSet集合
(1)底层是一个HashMap实现的。他用HashMap来保存HashSet的所有元素,会定义一个虚拟的Object对象来作为HashMap的value值,并且将此对象定义为static final。
源码:(莫等闲老哥解释的,搬来学习学习)
// 底层使用HashMap来保存HashSet中所有元素。
private transient HashMap<E,Object> map;
// 定义一个虚拟的Object对象作为HashMap的value,将此对象定义为static final。
private static final Object PRESENT = new Object();
/**
* 默认的无参构造器,构造一个空的HashSet。
*
* 实际底层会初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。
*/
public HashSet() {
map = new HashMap<E,Object>();
}
/**
* 构造一个包含指定collection中的元素的新set。
*
* 实际底层使用默认的加载因子0.75和足以包含指定
* collection中所有元素的初始容量来创建一个HashMap。
* @param c 其中的元素将存放在此set中的collection。
*/
public HashSet(Collection<? extends E> c) {
map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
(2)HashSet中元素不可重复,是用hashCode和equals方法来判断依据的。
1)如果hashCode相同,equals方法比较不同,那在内存中就会存在同一片区域(计算出hash地址相同,hashCode不是存储地址),那么就会顺次后延一位存入(实际上底层是一个数组和一个链表,首先地址相同找到要存放的数组的位置,然后比较equals,不同就放入链表中,相同不存)
2)如果hashCode相同,equals比较相同,则认定为相同元素,不允存储。
3)如果hashCode不同,那就直接认定不同元素,不会调用equals方法,直接存储
三、TreeSet集合
(0)TreeSet集合会根据存入元素自身的自然顺序进行排序,所以输出是一个有序的集合。
(1)其实TreeSet下面也是封装了一个TreeMap来实现的,跟HashSet类似,再底层就是一个红黑树来实现了(并不懂红黑树)。
(2)当TreeSet中没有指定泛型时候,且也没有自定义比较器,那么存入的元素就要是同一种类型的,否则就会报java.lang.ClassCastException,那是因为TreeSet内部是有序的,会有一个自动的排序比较过程。如果两种不同的类型,那么就不具有可比性,两种类型之间也不能强制相互转换。代码如下
TreeSet tSet = new TreeSet();
tSet.add(10);
tSet.add(15);
tSet.add(3);
tSet.add(1);
tSet.add(55);
tSet.add(36);
tSet.add(66);
// 不指定泛型的时候,存入的只能是同一个类型的数据,因为TreeSet内部是有序的,会有一个自动的排序比较过程。
// 如果是两种不同的类型,那就不具有可比性,自然抛出异常
tSet.add("jjj");
(3)自定义比较规则:
1)TreeSet集合中存入的自定义类型自身具有比较性(该类实现Comparable接口,重写compareTo方法,定义比较规则)。
public class Animal implements Comparable<Animal> {
private String name;
private String color;
private double weight;
public Animal() {}
public Animal(String name, String color, double weight) {
this.name = name;
this.color = color;
this.weight = weight;
}
@Override
public int compareTo(Animal o) {
// 判断错误输入null
if (this.name == null || this.color == null) {
throw new RuntimeException("属性值不能为null");
}
// 判断是否重复元素
if (this.name.equals(o.getName()) && this.color.equals(o.getColor()) && this.weight == o.getWeight()) {
return 0;
}
//按照体重排序
if (this.weight >= o.getWeight()) {
return 1; //正数排在后面
} else {
return -1; //负数排在前面
}
}
2)在声明一个TreeSet集合的时候,传入一个比较器,使用匿名内部类的方式实现Compartor接口,重写compare方法。
Set<Animal> treeSet = new TreeSet<>(new Comparator<Animal>() { //传入一个比较器 匿名内部的形式
@Override
public int compare(Animal o1, Animal o2) {
//判断属性值为null
if (o1.getName() == null || o1.getColor() == null) {
throw new RuntimeException("属性值不能为null");
}
//所有属性都相等时候,重复元素 不能存储
if (o1.getName().equals(o2.getName()) &&
o1.getColor().equals(o2.getColor()) &&
o1.getWeight() == o2.getWeight()) {
return 0;
}else {
if(o1.getWeight()>=o2.getWeight()) {
return -1;
}else {
return 1;
}
}
}
});
treeSet.add(new Animal("狼狗", "黑色", 30.5));
treeSet.add(new Animal("柴犬", "黄色", 11.0));
treeSet.add(new Animal("杜宾犬", "灰色", 20.0));
treeSet.add(new Animal("泰迪", "棕色", 8.0));
treeSet.add(new Animal("哈士奇", "黑白", 25.5));
treeSet.add(new Animal("哈士奇", "黑白", 25.5));
// treeSet.add(new Animal(null,"黑白",25.5));
for (Animal animal : treeSet) {
System.out.println(animal);
}