TreeSet是依靠TreeMap来实现的。TreeSet是一个有序集合,TreeSet中的元素将按照升序排列,缺省是按照自然排序进行排列,意味着TreeSet中的元素要实现Comparable接口。或者有一个自定义的比较器。TreeSet支持两种排序方法:自然排序和定制排序,TreeSet默认采用自然排序。
Java常用类实现Comparable接口,并提供了比较大小的标准。实现Comparable接口的常用类:
BigDecimal、BigIneger以及所有数值型对应包装类:按它们对应的数值的大小进行比较; Character:按字符的UNICODE值进行比较; Boolean:true对应的包装类实例大于false对应的包装类实例; String:按字符串中字符的UNICODE值进行比较; Date、Time:后面的时间、日期比前面的时间、日期大。
1、添加一个对象到TreeSet时,对象类必须实现Comparable接口,代码如下:
package com.yoodb; import java.util.TreeSet; public class Error{ public static void main(String[] args) { TreeSet<Object> set = new TreeSet<Object>(); set.add(new Error()); set.add(new Error()); } }
其结果报错,错误信息如下:
Exception in thread "main" java.lang.ClassCastException: com.Error cannot be cast to java.lang.Comparable at java.util.TreeMap.compare(TreeMap.java:1188) at java.util.TreeMap.put(TreeMap.java:531) at java.util.TreeSet.add(TreeSet.java:255) at com.Error.main(Error.java:9)
注意:
1)如果向TreeSet集合中添加2个Error对象,添加第一个对象时,TreeSet里没有任何元素因此没有问题,但添加第二个Error对象时TreeSet就会调用该对象的compareTo(Object obj)方法与集合中其他元素进行比较,如果对应的类没有实现Comparable接口,则会引发ClassCastException异常;从TreeSet中取出元素第一个元素时,依然会引发ClassCastException异常。
2)采用compareTo(Object obj)方法比较对象时,都需要将被比较对象obj强制类型转换成相同类型,因为只有相同类的两个实例才能比较大小。即向TreeSet中添加的应该是同一个类的对象,否则会引发ClassCastException异常。
3)当把一个对象加入TreeSet集合中时,TreeSet调用该对象的compareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑树算法决定它的存储位置,如果两个对象通过compareTo(Object obj)比较相等,TreeSet即认为它们存储同一位置。(如果对红黑树算法不是很了解可以去网上查询一下)
4)TreeSet集合判断两个对象不相等的标准:两个对象通过equals方法比较返回false,或通过compareTo(Object obj)比较没有返回0——即使两个对象时同一个对象,TreeSet也会把它们当成两个对象进行处理,具体代码如下:
package com.yoodb; import java.util.TreeSet; class A implements Comparable { int age; public A(int age) { this.age = age; } public boolean equals(Object obj) { return false; } public int compareTo(Object obj) { return 1; } } public class TestTreeSet { public static void main(String[] args) { TreeSet<Object> set = new TreeSet<Object>(); A a1 = new A(21); set.add(a1); System.out.println(set.add(a1)); System.out.println(set); ((A) (set.first())).age = 25; System.out.println(((A) (set.last())).age); } }
结果运行如下:
true [com.A@73cbc5cb, com.A@73cbc5cb] 25
1)把同一对象添加两次,a1对象的equals方法返回false,而且compareTo(Object obj)方法总是返回1,TreeSet会认为a1对象和它自己也不相同,因此TreeSet中添加两个a1对象。而TreeSet对象保存的两个元素实际上是同一个元素。所以当修改TreeSet集合里第一个元素的age属性后,该TreeSet集合里最后一个元素的age属性也随之改变了。
2)两个对象通过equals方法比较返回true,通过compareTo(Object obj)方法比较不返回0时TreeSet将会把这两个对象保存在不同位置,从而两个对象都可以添加成功;
3)两个对象通过equals方法比较返回false,通过compareTo(Object obj)方法比较返回0两个对象通过compareTo(Object obj)方法比较相等,TreeSet将试图把它们保存在同一个位置,但实际上又不行(否则将只剩下一个对象);
2、定制排序
实现定制排序需要在创建TreeSet集合对象时提供一个Comparator对象与该TreeSet集合关联,由该Comparator对象负责集合元素的排序逻辑。具体实现代码如下:
package com.yoodb; import java.util.Comparator; import java.util.TreeSet; class Age { int age; public Age(int age) { this.age = age; } public String toString() { return "age 对象 (age:" + age + ")"; } } public class TestTreeSet { public static void main(String[] args) { TreeSet<Object> ts = new TreeSet<Object>(new Comparator<Object>() { public int compare(Object o1, Object o2) { Age a1 = (Age) o1; Age m2 = (Age) o2; if (a1.age > m2.age) { return -1; } else if (a1.age == m2.age) { return 0; } else { return 1; } } }); ts.add(new Age(21)); ts.add(new Age(25)); ts.add(new Age(-9)); System.out.println(ts); } }
运行程序结果如下:
[M 对象 (age:25), M 对象 (age:21), M 对象 (age:-9)]
创建一个Comparator接口的匿名内部类对象,负责ts集合的排序。当把Age对象添加到集合中时,无须Age类实现Comparable接口,此时TreeSet无须通过Age对象来比较大小,而是由与TreeSet关联的Comparator对象来负责集合元素的排序。使用定制排序时,TreeSet对集合元素排序时不管集合元素本身的大小,而是由Comparator对象负责集合元素的排序规则。