【Effective Java 14】考虑实现 Comparable 接口
作者:互联网
1. 什么时候应该让类实现 Comparable 接口
Comparable
接口是一个泛型接口,代码如下:
public interface Comparable<T> {
int compareTo(T t);
}
类实现 Comparable
接口,就表明它的实例具有内在的排序关系,比如按照字母顺序、按数值顺序或者按年代顺序,那你就应该考虑实现 Comparable
接口:
一般来说,值类适合实现 Comparable
接口。
2. 使用 Comparable 接口的约定
将一个对象与另一个对象进行比较时。当对象小于、等于或大于指定对象的时候,分别返回一个负整数、零或正整数。如果由于指定对象的类型而无法与该对象进行比较,则抛出 ClassCastException
异常。
实现 Comparable 接口的类,在实现时应该满足以下约定(注意,在下面的说明中,符号 sgn(...) 表示数学中的 signum 函数,它根据表达式的值为负、零、正,分别返回-1,0,1):
- 实现者必须确保所有的 x 和 y 都满足
sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
(交换律) - 实现者必须确保比较关系可以传递:
(x.compareTo(y) > 0 && y.compareTo(z) > 0)
则x.comareTo(z) > 0
;(传递律) - 实现着必须确保
x.compareTo(y) == 0
则对于所有的z
都满足sgn(x.compareTo(z)) == sgn(y.compareTo(z))
- 强烈建议对于值类,满足
(x.compareTo(y) == 0) == (x.equals(y))
。但也并非绝对必要,如果违反这一条件,都应该在文档中明确予以说明。
注意,Comparable
接口不能跨越不同类型进行比较,在比较不同类型的对象时,可以在 compareTo
函数中抛出 ClassCastException
异常来拒绝执行。
违反 Comparable
接口约定会破坏其他依赖于比较关系的类或方法,如 TreeSet
、TreeMap
、工具类 Collections
,Arrays
,因为它们内部都用与排序相关的算法。
3. 使用 Comparable 接口的建议
- 在
compareTo
方法中使用关系操作符>
或<
去比较基本类型是十分繁琐的,或者使用-
加>0
判断。对于基本类型在compareTo
函 数中进行比较应该使用静态方法代替,如Integer.compare(a, b)
、Float.compare(a, b)
、Double.compare(a, b)
等等。
// 太繁琐
public int compare(Integer A, Integer B) {
if A > B {
return 1;
} else if A < B {
return -1;
} else {
return 0;
}
}
// 有溢出风险
public int compare(Integer A, Integer B) {
return A - B;
}
// 最佳
public int compare(Integer A, Integer B) {
return Integer.compare(A, B);
}
- 如果一个类有多个关键域,按照什么样的顺序来比较这些域是十分关键的。你必须从最关键的域开始,逐步进行到所有重要的域。
4. 使用 Comparator 接口
Comparator
接口配置了一组比较构造器方法,使得比较器的构造工作变得更加流畅。之后,按照Comparable
接口的要求,这些比较器可以用来实现一个compareTo
方法。如下:
private static class PhoneNumber implements Comparable<PhoneNumber>{
private final short areaCode;
private final short prefix;
private final short lineNum;
public PhoneNumber(short areaCode, short prefix, short lineNum) {
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNum = lineNum;
}
// 使用 Comparator 可以轻松实现对关键域按优先级进行比较
private static final Comparator<PhoneNumber> COMPARATOR =
Comparator
.comparingInt((ToIntFunction<PhoneNumber>) value -> value.areaCode)
.thenComparingInt(value -> value.prefix)
.thenComparingInt(value -> value.lineNum);
@Override
public int compareTo(PhoneNumber o) {
return COMPARATOR.compare(this, o);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PhoneNumber that = (PhoneNumber) o;
return areaCode == that.areaCode && prefix == that.prefix && lineNum == that.lineNum;
}
@Override
public int hashCode() {
return Objects.hash(areaCode, prefix, lineNum);
}
}
5. 总结
- 对于排序敏感类,都应该让该类实现 Comparable 接口
- 在
compareTo
函数中避免使用 > 或 < 操作符,不要通过减法运算去进行判断,有可能溢出 - 如果
compareTo
函数编写起来比较复杂,比如关键域较多的情况,应该单独实现一个静态Comparator
比较构造器,并通过它简介实现comparaTo
接口。
标签:Comparable,return,14,Integer,接口,prefix,compareTo,Java 来源: https://www.cnblogs.com/Silgm/p/16142123.html