目录

  • 1.前言
  • 2.实现Comparable接口
  • 3.重写Comparator(比较器)方法
  • 4.总结
  • 5.参考文献


1.前言

由下面的继承结构图可以看出来,TreeSet继承了SortedSet的特性,而SortedSet接口又定义了一个comparator抽象方法,返回一个Comparator,这意味着TreeSet的底层是可以自动进行排序的。

但是java中只对Integer和String类型的数据结构重写了Comparator方法,如果我们想要自定义一个数据结构,那么如何让TreeSet实现我们想实现的排序方法呢?

有两种方案,正如题目说的那样,实现Comparable接口或者重写Comparator(比较器)方法都可以实现我们的需求,接下来我们将一步一步地解析。

equlase hascode java map 重写 重写了什么 java重写comparator_设计模式

equlase hascode java map 重写 重写了什么 java重写comparator_设计模式_02


equlase hascode java map 重写 重写了什么 java重写comparator_java_03

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);
        }

    }
}

运行结果:

equlase hascode java map 重写 重写了什么 java重写comparator_比较器_04


可以看到,我们实现的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使用。

equlase hascode java map 重写 重写了什么 java重写comparator_字符串_05


让我们再看看TreeSet的add方法,在调用add方法的时候底层会去调用TreeMap的put方法,传入一个我们需要传入的元素e,但是Map是以键值对的形式存在的,所以存入的时候比如要存入一个键值,这里的PRESENT只是一个占位符,并没有什么实际的意义。

equlase hascode java map 重写 重写了什么 java重写comparator_设计模式_06


让我们再看看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);
        }

    }
}

运行结果:

equlase hascode java map 重写 重写了什么 java重写comparator_字符串_07


可以看到两种实现方法都可以达成我们的需求。

4.总结

可以看出,两种方法都可以实现相同的功能,但它们又有各自的优缺点,比如,单独实现一个comparator比较器不仅仅可以运用在一个类中,如果设计好,通过一些简单地重写,我们可以将它广泛扩展运用在不同的类中,而这也就是面向接口、面向对象编程的精华所在。

5.参考文献

1.java集合继承关系图