TreeSet集合解析
TreeSet是实现Set接口的实现类。所以它存储的值是唯一的,同时也可以对存储的值进行排序,排序用的是二叉树原理。所以要理解这个类,必须先简单理解一下什么是二叉树。
二叉树原理简析
假如有这么一个集合TreeSet<Integer>是[5,11,6,5,23,14]
用二叉树是怎么排序的呢?
二叉树遍历方法比较多,有兴趣自己百度看下吧。这里只需要知道元素是怎么插入到二叉树即可。小的存储在左边(负数),大的存储在右边(正数),相等不存储。
TreeSet的基本使用
@Test public void test() { Set<Integer> set = new TreeSet<Integer>(); set.add(2); set.add(22); set.add(24); set.add(2); for (int i : set) { System.out.println(i); } }
输出结果
2 22 24
可以知道,TreeSet集合不仅可以保证集合元素的唯一性,还可以排序。
如果TreeSet里面存储的是对象呢?会出现什么情况呢?
public static void main(String[] args) { User u1 = new User("Kevin", 72); User u2 = new User("Jack", 22); User u3 = new User("Erik", 32); User u4 = new User("Blank", 72); Set<User> set = new TreeSet<User>(); set.add(u1); set.add(u2); set.add(u3); set.add(u4); for (User u : set) { System.out.println(u); } }
输出结果
Exception in thread "main" java.lang.ClassCastException: com.kevin.exercise10.User cannot be cast to java.lang.Comparable at java.util.TreeMap.put(TreeMap.java:542) at java.util.TreeSet.add(TreeSet.java:238) at com.kevin.exercise10.TreeSetTest.main(TreeSetTest.java:23)
报错了,因为集合里面的是对象,对象不能转换为比较可比较对象。
如果想根据年龄排序,打印出各个对象(toString方法),应该怎么做呢?在API里面搜索一下Comparable,发现是个接口,那么我们就可以让Students类实现Comparable接口方法,这样Students对象就成为了可比较对象了。
User类实现Comparable接口方法:
public class User implements Comparable<User> { private String name; private int age; @Override public String toString() { return "User{" + "name='" + name + '\'' + ", 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(User u) { int num = this.age - u.age; return this.age - u.age; } public User(String name, int age) { this.name = name; this.age = age; } public User() { } }
为什么是this.age-u.age?this.age代表调用时的对象的age,返回的如果是正数(比u.age大),就存储在右边。返回的是如果是负数(比u.age小),就存储在左边。如果等于0,就不存储。
这就出问题了,如果两个人不同名字,同样年龄,this.age - o.age = 0,不就存不进二叉树了吗?
验证一下:
public static void main(String[] args) { User u1 = new User("Kevin", 72); User u2 = new User("Jack", 22); User u3 = new User("Erik", 32); User u4 = new User("Blank", 72); Set<User> set = new TreeSet<User>(); set.add(u1); set.add(u2); set.add(u3); set.add(u4); for (User u : set) { System.out.println(u); } }
输出结果:
User{name='Jack', age=22} User{name='Erik', age=32} User{name='Kevin', age=72}
所以需要改进一下User类的compareTo方法,保证同年龄,但是不同名字的学生也能存进二叉树。
@Override public int compareTo(User u) { int num = this.age - u.age; return num == 0 ? this.name.compareTo(u.name) : num; }
这样就可以把四个不同的对象存储进来,并且先按照年龄排序,年龄相同的再按照字符串排序。
输出结果:
User{name='Jack', age=22} User{name='Erik', age=32} User{name='Blank', age=72} User{name='Kevin', age=72}
---------------------------------------------------------------------------------------
除了这种方式可以实现排序以外,还有一种方式可以实现排序。
TreeSet有这么一个构造方法:
TreeSet(Comparator<? super E> comparator) 构造一个新的,空的树集,根据指定的比较器进行排序。
Comparator是什么呢?API文档看一下:Interface Comparator<T>,是一个接口,里面有一个要实现的接口方法:int compare(T o1, T o2) 比较其两个参数的顺序。
例如,我们要对字符串的长度进行排序,长度相同的安装字符串排序:
public class TreeSetTest2 { public static void main(String[] args) { TreeSet<String> set = new TreeSet<String>(new SortedByLen()); set.add("abc"); set.add("abcd"); set.add("b"); set.add("a"); set.add("abab"); System.out.println(set); } } class SortedByLen implements Comparator<String> { @Override public int compare(String s1, String s2) { int num = s1.length() - s2.length(); return num == 0 ? s1.compareTo(s2) : num; } }
需要注意的是重写compare方法的o1,o2。o1代表调用的对象,o2代表集合中的对象。两种实现排序方式视情况而用。
(1)自然顺序(Comparable)
- TreeSet类的add()方法中会把存入的对象提升为Comparable类型
- 调用对象的compareTo()方法和集合中的对象比较
- 根据compareTo()方法返回的结果进行存储
(2)比较器顺序(Comparator)
- 创建TreeSet的时候可以指定一个Comparator
- 如果传入了Comparator的子类对象,那么TreeSet就会按照比较器中的顺序排序
- 调用的对象是compare方法的第一个参数,集合中的对象是compare方法的第二个参数
(3)两种方式的区别
- TreeSet构造函数什么都不传,默认按照类中Comparable的顺序(没有就报错ClassCastException)
- TreeSet如果传入Comparator,就优先按照Comparator
如果不想保证元素的唯一性,改一下compare方法就可以了,永远不要让它返回0。
原文参考【https://www.javazhiyin.com/】