Java 笔记 —— Set 集合的排序原理,附答案 + 考点
for(Student2 s : tree){
System.out.println(s.getName()+"---"+s.getAge());
}
}
}
结果是报错
所以,现在来看看 TreeSet 的相关源码
相关源码
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>{
private transient NavigableMap<E,Object> m;
//调用无参构造方法生成的 TreeSet 对象,底层实际上是创建了一个 TreeMap 对象
public TreeSet() {
this(new TreeMap<E,Object>());
}
//TreeSet 的 add 方法,实际上实现了 NavigableMap<K,V>
//这里的 m 是无参构造 TreeSet 时构造的一个 TreeMap 对象,所以 put 方法也是 TreeMap 中的方法。
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
}
public class TreeMap<K,V> implements NavigableMap<K,V>{
//如果是无参构造,也就是自然排序,则 comparator 排序器为 null
public TreeMap() {
comparator = null;
}
public V put(K key, V value) {
//生成一个根
Entry<K,V> t = root;
//在还没有元素插入的时候,树的根是 null,然后生成一个根
if (t == null) {
compare(key, key);
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
//由于是无参构造,comparator 的值是 null
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
//key 就是想要添加到集合中的元素值
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
//这里进行了强制类型转换,向下转型
//Comparable 是自然比较
//这里要求了传入的对象的类必须实现了 Comparable<T>接口
Comparable<? super K> k = (Comparable<? super K>) key;
/*
这里是把要传入集合中的元素 key,进行类型强制转换后,变成 k
第一个传入的元素会被作为第一个根节点
之后传入的元素 k 与树的每一个根节点进行比较,所以这里是 do while 循环,所有根节点都要比较
这里是借助 compareTo 方法来做比较,格式为: 要插入的元素.compareTo(集合原来的元素)
如果 k 的值小于根节点,则作为左孩子,加入树的左侧
如果 k 的值大于根节点,则作为右孩子,加入树的右侧
如果 k 的值与根节点相同,则说明元素重复,不会加入集合中
*/
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
}
这里具体的过程可以拿第一个例子,传入的对象是 Integer 类型的例子来说明
自定义类继承 Comparable 接口
现在回过头来,为什么 Integer 可以实现自然排序,因为 Integer 类已经实现 Comparable 接口
那么如果想让自定义类的对象也可以实现自然排序,就需要让自定义类也实现 Comparable 接口
package review.SetDemo;
public class Student2 implements Comparable<Student2>{
private String name;
private int age;
public Student2() {
}
public Student2(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Student2 o) {
int i = this.age - o.age;
//先比较年龄是否相同,如果不同则返回年龄的比较结果
//如果年龄相同则比较姓名是否相同
//姓名相同则返回 0,说明两个元素相同
//姓名不同则按照 this.name.compareTo(o.name)返回的结果排序
int i2 = i==0 ? this.name.compareTo(o.name) : i;
return i2;
}
}
package review.SetDemo;
import java.util.TreeSet;
public class demo4 {
public static void main(String[] args) {
TreeSet<Student2> tree = new TreeSet<>();
Student2 s1 = new Student2("zhang",12);
Student2 s2 = new Student2("zhou",15);
Student2 s3 = new Student2("cao",16);
Student2 s4 = new Student2("zhang",23);
Student2 s5 = new Student2("zhang",12);
Student2 s6 = new Student2("jing",15);
tree.add(s1);
tree.add(s2);
tree.add(s3);
tree.add(s4);
tree.add(s5);
tree.add(s6);
for(Student2 s : tree){
System.out.println(s.getName()+"---"+s.getAge());
}
}
}
结果为
去重成功,对象 s5 没有加入到集合中
而姓名相同的对象 s1 和对象 s4,则是按照年龄大小进行排序
而年龄相同的对象对象 s2 和对象 s6,则是按照姓名进行排序
当然这里也可以加入更多的条件
重写的 compareTo 方法中,不止要有排序的判断,还要有元素是否相同的判断
比如这里,排序的判断是优先判断名字的长度
后面的 i2 和 i3 都有两个功能:
功能一、判断元素是否相同,如果相同则返回 0
功能二、如果元素不相同,则按照对应的属性值进行排序
比如这里 i2 比较的姓名,如果不同则直接按这个比较结果排序,不会看年龄
如果 i2 返回的结果为 0,说明姓名相同,这时会用 i3 中的年龄的比较结果进行排序
如果 i3 返回的结果也是 0,说明所有属性值都相同,此时应该认为元素相同,不加入到集合中
@Override
public int compareTo(Student2 o) {
//先比较姓名的长度
int i = this.name.length() - o.name.length();
//姓名的长度相同不代表内容也相同
int i2 = i == 0 ? this.name.compareTo(o.name) : i;
//姓名的长度和内容都一样,但是年龄可能不一样
//只有当姓名和年龄都一样的时候才判断为同一个人,才算重复
int i3 = i2 == 0 ? this.age - o.age : i2;
return i3;
}
比较器排序
**注意:
自然排序的类里面实现的是 Comparable<>接口,里面重写的方法是 compareTo(Object obj)
比较器排序的类实现的是 Comparator 接口,里面重写的方法是 compare(Object o1, Object o2)**
实现比较器排序,需要在创建集合的时候,调用带参数的构造方法,这个参数是实现了 Comparator 接口的子类对象
用匿名内部类的形式实现 Comparator 接口
注意看注释部分
package review.SetDemo;
import java.util.Comparator;
import java.util.TreeSet;
public class demo5 {
public static void main(String[] args) {
TreeSet<Student3> tree = new TreeSet<>(new Comparator<Student3>() {
评论