day16-Set集合
一、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); }