目录
- 1.前言
- 2.实现Comparable接口
- 3.重写Comparator(比较器)方法
- 4.总结
- 5.参考文献
1.前言
由下面的继承结构图可以看出来,TreeSet继承了SortedSet的特性,而SortedSet接口又定义了一个comparator抽象方法,返回一个Comparator,这意味着TreeSet的底层是可以自动进行排序的。
但是java中只对Integer和String类型的数据结构重写了Comparator方法,如果我们想要自定义一个数据结构,那么如何让TreeSet实现我们想实现的排序方法呢?
有两种方案,正如题目说的那样,实现Comparable接口或者重写Comparator(比较器)方法都可以实现我们的需求,接下来我们将一步一步地解析。
2.实现Comparable接口
现在我们有一个node类,里面有一个content属性储存了字符串,现在我将定义一个TreeSet,指定数据类型为node,需求是我想根据node中content字符串的长度大小来实现TreeSet元素的排序。
现在我有一个Person类,里面有name和age的属性,现在我将定义一个TreeSet,指定数据类型为Person,需求是根据age来排序,如果年龄相同,则根据name的字典顺序来排序。
第一种方法就是实现Comparable接口,让Person类继承Comparable接口,然后重写compareTo方法。
class Person implements Comparable<Person>{
String name;
int age;
public Person(String name,int age){
this.name =name;
this.age = age;
}
@Override
public String toString(){
return "Person:name="+this.name+" age="+this.age;
}
//treeSet在add一个元素的时候都会在内部调用多次这个方法,让新填入的元素与这个元素两两进行比较,最终确定新添入的元素到底放在哪里
@Override
public int compareTo(Person p1) {
if(this.age == p1.age){
//因为字符串已经实现了compareTo方法
return this.name.compareTo(p1.name);
}else{
return this.age -p1.age;
}
}
}
创建完对象后实例化几个例子去试试
import java.util.TreeSet;
public class Testfile{
public static void main(String[] args) {
TreeSet<Person> set =new TreeSet<>();
//初始化几个实例
set.add(new Person("A",14));
set.add(new Person("E",15));
set.add(new Person("F",12));
set.add(new Person("B",11));
set.add(new Person("G",17));
set.add(new Person("H",15));
set.add(new Person("A",10));
set.add(new Person("C",19));
for (Person person : set) {
System.out.println(person);
}
}
}
运行结果:
可以看到,我们实现的Compatable已经很好地实现了compareTo方法,完成了比较的功能。
3.重写Comparator(比较器)方法
先让我们看看TreeSet的构造函数
public TreeSet<E>{
//1.无参构造,底层会创建一个新的TreeMap对象
public TreeSet() {
this(new TreeMap<>());
}
//2.传入一个comparator,并且实例化一个TreeMap对象,把Comparator比较器放进去
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
//3.暂时不理会
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
//4.传入一个实现SortedSet的Set,运用该set的比较器
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
}
3和4我们暂时不用理会,让我们来看1和2,我们发现,不管我们怎么做,TreeSet底层都会先创建的一个TreeMap对象,并将比较器传入TreeMap中,归TreeMap使用。
让我们再看看TreeSet的add方法,在调用add方法的时候底层会去调用TreeMap的put方法,传入一个我们需要传入的元素e,但是Map是以键值对的形式存在的,所以存入的时候比如要存入一个键值,这里的PRESENT只是一个占位符,并没有什么实际的意义。
让我们再看看TreeMap在调用put方法的时候底层是什么样的
public V put(K key, V value) {
//我挑出一段该方法的重点来解释
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);
}
//如果没有比较器,且也没有实现Comparable接口的话,就会抛出异常
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
//如果实现了Comparable接口的compareTo方法,就用该方法去比较
Comparable<? super K> k = (Comparable<? super K>) key;
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);
}
}
上面的结论表明,我们在treeSet对象初始化的时候,就可以传入一个Comparator比较器,用来对treeSet内部的元素进行比较。
下面是关于comparator的实现:
//还是运用上面的例子,我们来定义一个比较器,实现Comparator中的compare方法
class Mycomparator implements Comparator<Person>{
//重写比较方法
@Override
public int compare(Person p1, Person p2) {
if(p1.age == p2.age){
return p1.name.compareTo(p2.name);//因为字符串已经实现了compareTo方法
}else{
return p1.age -p2.age;
}
}
}
现在我们取消Person对Comparable的实现,也就是说Person现在没有比较功能了。
class Person {
String name;
int age;
public Person(String name,int age){
this.name =name;
this.age = age;
}
@Override
public String toString(){
return "Person:name="+this.name+" age="+this.age;
}
}
最后我们再初始化:
import java.util.Comparator;
import java.util.TreeSet;
public class Testfile{
public static void main(String[] args) {
//初始化treeSet的时候传入我们刚才重写好的Mycomparator比较器
TreeSet<Person> set =new TreeSet<Person>(new Mycomparator());
//初始化几个实例
set.add(new Person("A",14));
set.add(new Person("E",15));
set.add(new Person("F",12));
set.add(new Person("B",11));
set.add(new Person("G",17));
set.add(new Person("H",15));
set.add(new Person("A",10));
set.add(new Person("C",19));
for (Person person : set) {
System.out.println(person);
}
}
}
运行结果:
可以看到两种实现方法都可以达成我们的需求。
4.总结
可以看出,两种方法都可以实现相同的功能,但它们又有各自的优缺点,比如,单独实现一个comparator比较器不仅仅可以运用在一个类中,如果设计好,通过一些简单地重写,我们可以将它广泛扩展运用在不同的类中,而这也就是面向接口、面向对象编程的精华所在。
5.参考文献
1.java集合继承关系图