浅谈compareTo()方法

下面这段代码使用了 compareTo() 方法,有问题吗?

1
2
3
4
5
6
7
8
9
class Employee implements Comparable {
private int id;

@Override
public int compareTo(Object o) {
Employee emp = (Employee) o;
return this.id - emp.id;
}
}

使用减法看起来合乎逻辑,但实际上隐藏了溢出的问题。

当我们需要按照一定的规则进行排序的时候,通常要实现 Comparable 接口,并实现 compareTo 方法,规则如下:

1)如果当前对象小于另外一个对象,则 compareTo 方法必须返回负数;如果当前对象大于另外一个对象,则必须返回正数;如果两个对象相等,则返回零。

2)通常来说,compareTo 方法必须和 equals 方法一致,如果两个对象通过 equals 方法判断的结果为 true,那么 compareTo 必须返回零。

不过,JDK 中有一个反例,就是 BigDecimal

1
2
3
4
5
BigDecimal bd1 = new BigDecimal("2.0");
BigDecimal bd2 = new BigDecimal("2.00");

System.out.println("equals: " + bd1.equals(bd2));
System.out.println("compareTo: " + bd1.compareTo(bd2));

输出结果如下所示:

1
2
equals: false
compareTo: 0

这是因为 JDK 认为 2.02.00 的精度不一样,所以不能 equals,但值确实是相等的。

3)不能使用减法来比较整数值,因为减法的结果可能溢出。应该使用 Integer.compareTo() 来进行比较。如果你想通过减法操作来提高性能,必须得确保两个操作数是正整数,或者确保两者相差的值小于 Integer.MAX_VALUE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class CompareDemo {
public static void main(String[] args) {
List<Employee> list = new ArrayList<>();
list.add(new Employee(1));
list.add(new Employee(Integer.MIN_VALUE));
list.add(new Employee(Integer.MAX_VALUE));
Collections.sort(list);
System.out.println(list);
}
}

class Employee implements Comparable {
private int id;

public Employee(int id) {
this.id = id;
}

@Override
public int compareTo(Object o) {
Employee emp = (Employee) o;
return this.id - emp.id;
}

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

程序的输出结果如下所示:

1
[Employee{id=1}, Employee{id=2147483647}, Employee{id=-2147483648}]

排序就乱了。因为 Integer.MIN_VALUE - 1 变成了正数 2147483647

回到最开始的问题,正确的写法应该是

1
2
3
4
5
6
7
8
9
class Employee implements Comparable {
private int id;

@Override
public int compareTo(Object o) {
Employee emp = (Employee) o;
return Integer.valueOf(this.id).compareTo(emp.id);
}
}

实际上,Integer.compareTo()底层是通过调用Integer.compare()方法实现的:

1
2
3
4
5
6
7
8
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
2
3
4
5
6
7
8
9
class Employee implements Comparable {
private int id;

@Override
public int compareTo(Object o) {
Employee emp = (Employee) o;
return Integer.compare(this.id, emp.id);
}
}

值得注意的是,String类型只有compareTo方法,而没有compare方法。给出源代码进行参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;

int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}

可见String类型不需要compare方法。

基于这10道基础Java面试题,虐哭了多少人改写。

参考:

List集合排序Collections.sort()方法的一个容易忽略的小问题