Java的Comparable接口

这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

之前有一篇文章,是 《Java 中 Map 的常见用法都在这了》(juejin.cn/post/702668…) :把 Map 的用法,用到了装一个类:

public class TestMap2 {
    public static void main(String[] args) {

        Employee e1 = new Employee(666, "Fang", 66666);
        Employee e2 = new Employee(667, "张三", 9999999); // 因为他狂
        Employee e3 = new Employee(668, "萌叔", 36888);

        Map<Integer, Employee> map = new TreeMap<>();
        map.put(666, e1);
        map.put(667, e2);
        map.put(668, e3);
        Employee emp = map.get(666); // 返回的就是对象
        System.out.println(emp.getName());
    }
}

复制代码

像上面这种情况定义的类,后续就可以结合 Comparable 接口,实现对 Map 的自定义排序。这也是 Python 爱好者学习 Java 的哇塞时刻。

先说说 Comparable 接口

官方文档里是这样说的:
image.png

Compares this object with the specified object for order. Returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.

翻译一下,就是说,将 compareTo(T o) 这里的 o 对象与特定的对象进行比较:

  • 返回负整数,说明 this.value < o.value
  • 返回零,说明 this.value = o.value
  • 正整数,说明 this.value > o.value

还有一句话是说用了数学符号里的正负号函数,所以,负整数就取 -1,0 就是0,1代表正数。

In the foregoing description, the notation sgn(expression) designates the mathematical signum function, which is defined to return one of -10, or 1 according to whether the value of expression is negative, zero or positive.

来看具体是如何定义的

public class TestTreeSet {
    public static void main(String[] args) {
        Set<Integer> set = new TreeSet<>();

        set.add(300);
        set.add(200);
        set.add(138);
        set.add(250);

        for (Integer m: set) {
            System.out.println(m);
        }

        Set<Emp2> set2 = new TreeSet<>();
        set2.add(new Emp2(100,"hh",66));
        set2.add(new Emp2(110,"hh3",686));
        set2.add(new Emp2(15,"hh1",56));

        for (Emp2 m: set2) {
            System.out.println(m);
        }
    }
}

class Emp2 implements Comparable<Emp2> {
    int id;
    String name;
    double salary;

    @Override
    public String toString() {
        return "id=" + id +
                ", name='" + name +
                ", salary=" + salary;
    }

    public Emp2(int id, String name, double salary) {
        super();
        this.id = id;
        this.name = name;
        this.salary = salary;
    }

    @Override
    public int compareTo(Emp2 e) {
        // <0 小; 0 =; >0 正数
        if (this.salary >e.salary) {
            return 1;
        } else if(this.salary < e.salary) {
            return -1;
        } else {
            if (this.id > e.id) {
                return 1;
            } else if (this.id < e.id) {
                return -1;
            } else {
                return 0;
            }
        }
    }
}
复制代码

运行结果如下:
iamge.png
可以看到加入元素进去时,放的时候是无序的,但是打印 这个 TreeSet 就有序了!

弯路

弄懵我的地方,还得由源码来解释。

Set<Emp2> set2 = new TreeSet<>(); 这里必须是“TreeMap”或者“TreeSet”才可以实现排序效果。

下图是 TreeMap 的结构图(IDEA 里 command+7召唤出来的 Structure视图):
image.png

就可以看到有用到这个compare()接口。

final int compare(Object k1, Object k2) {
    return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
        : comparator.compare((K)k1, (K)k2);
}
复制代码

这里又用到了 Comparable。

HashMap 里的这部分是这样子的:
image.png
只有 comparableClassFor(Object):Class<?>
compareComparables(Class<?>, Object, Object):int

再双击,打开看一眼:

comparableClassFor(Object):Class<?>


/**
 * Returns x's Class if it is of the form "class C implements
 * Comparable<C>", else null.
 */
static Class<?> comparableClassFor(Object x) {
    if (x instanceof Comparable) {
        Class<?> c; Type[] ts, as; ParameterizedType p;
        if ((c = x.getClass()) == String.class) // bypass checks
            return c;
        if ((ts = c.getGenericInterfaces()) != null) {
            for (Type t : ts) {
                if ((t instanceof ParameterizedType) &&
                    ((p = (ParameterizedType) t).getRawType() ==
                     Comparable.class) &&
                    (as = p.getActualTypeArguments()) != null &&
                    as.length == 1 && as[0] == c) // type arg is c
                    return c;
            }
        }
    }
    return null;
}
复制代码

compareComparables(Class<?>, Object, Object):int


/**
 * Returns k.compareTo(x) if x matches kc (k's screened comparable
 * class), else 0.
 */
@SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable
static int compareComparables(Class<?> kc, Object k, Object x) {
    return (x == null || x.getClass() != kc ? 0 :
            ((Comparable)k).compareTo(x));
}
复制代码

都和它在放进元素排序的过程没有关系。

小匚的求知提问

请问你在项目中会用到 Comparable 接口嘛?
还是说大部分处理的数据都是上游处理好,按 SQL 中的排序函数排序过了?