之前有一篇文章,是 《Java 中 Map 的常见用法都在这了》:把 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 接口

官方文档里是这样说的:Java 的 Comparable 接口_Java

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 ​​-1​​​, ​​0​​​, 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;
}
}
}
}

运行结果如下:Java 的 Comparable 接口_自定义排序_02可以看到加入元素进去时,放的时候是无序的,但是打印 这个 TreeSet 就有序了!

弯路

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

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

下图是 TreeMap 的结构图(IDEA 里 command+7召唤出来的 Structure视图):Java 的 Comparable 接口_ide_03

就可以看到有用到这个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 里的这部分是这样子的:Java 的 Comparable 接口_Java_04只有 ​​​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 中的排序函数排序过了?