TreeMap集合类中的Comparator与Comparable接口详解

1.comparable接口

当我们运行关于treeMap示例代码1:

代码示例
package com.test.ComparatorTest;

import java.util.*;

public class Test {
    public static void main(String[] args) {
        Map students = new TreeMap();
        students.put(02,"邱烁铭");
        students.put(01,"李家雄");
        students.put(04,"李文高");
        students.put(03,"周云龙");

        Set st = students.keySet();     //获取键的集合
        Iterator it = st.iterator();       //获取Iterator对象
        while(it.hasNext()){
            Object key = it.next();
            Object value = students.get(key);       //根据获取的键值找到对应的值
            System.out.println(key + ":" + value);
        }
    }
}

结果如下:
TreeMap集合类中的Comparator与Comparable接口详解

通过分析结果发现:
◆ 对插入的键值对按照键的升序进行了排序。
◆ TreeMap类是一个无序集合(输出顺序与插入顺序不同)

造成此种现象的玄机就在TreeMap类的put方法中,put方法的部分源码如下所示:

public V put(K key, V value){
    ……
    Entry<K,V> t = root;
    if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                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);
    ……
}

TreeMap底层数据结构依赖于红黑树的实现,其核心在于比较待插入键值对中键的大小。因此,在put方法中我们将聚焦于compareTo方法。由于compareTo()是由变量k进行调用(k.compareTo()),不难发现,compareTo()是key强转后Comparable类型中的方法。查看comparable接口源码如下:

public interface Comparable<T> {
    public int compareTo(T o);
}

comparable接口中只有一个compareTo(T o)的抽象方法。结合示例代码,我们put的键值对为Integer-String类型,通过观察Integer类发现其为comparable接口的实现类。Integer类部分源码如下所示:

public final class Integer extends Number implements Comparable<Integer> {
                    ……
    public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }

    public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }       
                    ……
}

总结:
实例1中TreeMap类元素之所以能够排序是因为每次向TreeMap集合中存入键值对时,就会将键值取出通过调用compareTo()方法与其他元素的键值进行比较,该方法是Comparable接口中定义的。因此想要对集合中的元素进行排序,就必须实现Comparable接口。JDK中大部分类都实现Comparable接口,拥有接口中的CompareTo()方法,如Integer,String和double等。

2.comparator接口

有时候,定义的类没有实现Comparable接口或者实现了Comparable接口而不想按照定义的compareTo()方法进行排序。例如,希望按照降序进行排序。这时可以通过自定义比较器方式对TreeMap集合中的元素排序,即实现Comparator接口,在创建TreeMap集合时指定比较器。如实例2所示:

public class Test {
    public static void main(String[] args) {
        Map students = new TreeMap(new Decreasing());
        students.put(02,"邱烁铭");
        students.put(01,"李家雄");
        students.put(04,"李文高");
        students.put(03,"周云龙");

        Set st = students.keySet();
        Iterator it = st.iterator();
        while(it.hasNext()){
            Object key = it.next();
            Object value = students.get(key);
            System.out.println(key + ":" + value);
        }
    }
}

Decreasing比较器源码如下:

import java.util.Comparator;

public class Decreasing implements Comparator {

    @Override
    public int compare(Object o1, Object o2) {
        int id1 = (Integer) o1;
        int id2 = (Integer) o2;
        return (id2 < id1) ? -1 : ((id1 == id2) ? 0 : 1);
    }
}

运行结果如下:
TreeMap集合类中的Comparator与Comparable接口详解

通过结果对比发现,集合按照key值的降序进行排列。实例2中定义了比较器Decreasing针对Integer类型的key进行比较。其实现过程分析如下:
由于我们在创建TreeMap对象使传入了比较器实例,因此在put方法中,将会运行以下代码:

    public V put(K key, V value) {
            …………
        Entry<K,V> t = root;
        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);
        }
            …………
    }

根据源码分析,在比较键值时,会调用自定义比较器Decreasing中的compare方法对待插入节点的键值与其他节点的键值进行比对。然后按照自定义比较器制定的规则将节点插入到合适的位置。

总结:
实例2中TreeMap类元素之所以能够排序是因为每次向TreeMap集合中存入键值对时,就会将键值取出通过调用compare()方法与其他元素的键值进行比较,该方法是实现了Comparator接口的自定义比较器定义的。自定义比较器实现compare方法,并在创建TreeMap对象时传入。在用户层面,就能按照定义的compare规则对TreeMap集合进行排序。