第12条:考虑实现Comparable接口

compareTo方法并没有在Object中声明。相反,它是Comparable接口中唯一的一个方法。compareTo方法不但允许进行简单的同等性比较,而且允许执行顺序比较,除此之外,它与Object的equals方法具有相似的特性,还是个涉及到泛型的方法。类实现了Comparable接口,就表明它的实例具有内在的排序关系(natural ordering)。给实现Comparable接口的对象数组进行排序,只需要下面这一行代码:

Arrays.sort(a);

对于存储在集合中的Comparable对象进行搜索、计算极限值以及自动维护也是同样的简单。例如下面的程序依赖于String实现了Comparable接口,它去掉了命令行参数列表中的重复参数,并按字母表顺序打印出来了:

public class WordList {

public static void main(String[] args) {

Set s = new TreeSet();

Collection.addAll(s, args);

System.out.println(s);

}

}

一旦类实现了Comparable接口,它就可以跟许多泛型算法以及依赖于该接口的集合实现进行协作。你付出了很小的努力就可以获得非常强大的功能。事实上,Java平台类库中的所有值类都实现了Comparable接口。如果我们需要写一个类时,当这个类有非常明显的内在排序关系,我们就应该优先考虑实现这个接口:

public interface Comparable {

int compareTo(T t);

}

Comparable接口的规范

compareTo方法的通用约定和equals方法的很相似,将一个对象与指定对象进行比较。当该对象小于、等于或者大于指定对象的时候,分别返回一个负整数、零或者正整数。如果由于指定对象的类型而无法和该对象进行比较,则抛出ClassCastException异常。

在下面的说明中,符号sgn(表达式)表示数学中的signum函数,它根据表达式的值为负值、零和正值,分别返回-1、0、1。

确保所有的属性都满足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。当y.compareTo(x)抛出异常时,x.compareTo(y)也要抛出异常。这条规则和equls规范里面的对称性类似。

必须确保比较关系是可传递的:(x.compareTo(y) > 0 && y.compareTo(z) > 0)同时x.compareTo(z) > 0也成立。对应着equals使用规范里面的传递性

必须确保x.compareTo(y) == 0同时所有的z都满足sgn(x.compareTo(z)) == sgn(y.compareTo(z))。

强烈建议(x.compareTo(y) == 0) == (x.equals(y)),但是并不是绝对必要的。如果一个类实现了Comparable接口,并且违反了这个条件,我们应该明确予以说明。推荐使用这样的说法:“注意,该类具有内在的排序功能,但是与equals不一致”。

在类的内部,任何合理的顺序关系都可以满足compareTo的约定。在跨越不同类的时候,compareTo可以不做比较:如果两个被比较的对象引用不同的对象,compareTo可以抛出ClassCastException异常。通常,这正是compareTo在这种情况下应该做的事情,如果类设置了确定的参数,这也正式它要做的事情。虽然以上约定没有把跨类之间的比较排除在外,但是从Java1.6发行版本开始,Java平台类库中就没有支持跨类比较的这种特性了。

就好像违反了hashCode约定的类会破坏其他依赖于散列算法的做法的情况一样,违反了compareTo约定的类也会破坏其他依赖于比较关系的类。依赖于比较关系的类包括有序集合类TreeSet和TreeMap、以及工具类Collections和Arrays,它们内部包含有搜索和算法排序。

上面的三个条款的一个直接结果就是,有compareTo方法施加的同等性测试,也一定遵守相同于equals约定所施加的限制条件:自反性、对称性和传递性。因此,下面的告诫也同样的适用:无法在用新的值组件扩展可实例化的类时,同时保持compareTo约定,除非愿意放弃面对对象的抽象优势。如果你想为一个实现了Comparable接口的类增加值组件,不扩展这个类;要便携一个不相关的类,其中包含第一个类的一个实例。然后提供一个“视图(view)”方法返回这个实例。这样既可以让你自由地在第二个类上实现compareTo方法,同时也允许它的客户端在必要的时候,把第二个类的实例看作第一个类的实例。运用组合优于继承

compareTo约定的最后一段是一个强烈的建议,而不是真正的规则,只是说明了compareTo方法施加的同等性测试,在通常情况下就应该返回与equals方法同样的结果。如果遵守了这一条规定,那么由compareTo方法所施加的关系顺序就会被认为”于equals一致”。如果违反了这条规则,则会相反。如果一个类的compareTo方法施加了一个与equals方法不一致的顺序关系,它仍然能够工作,但是如果有一个有序集合包含了该类的元素,这个集合就可能无法遵守相应集合接口(Collection、Set或Map)的通用约定。这是因为,对于这些接口的通用约定是按照equals方法来定义的,但是有序集合时使用由compareTo方法而不是equals方法所施加的同等性测试。

例如,考虑BigDecimal类,它的compareTo方法和equals方法不一致。如果你创建了一个HashSet实例,并且添加了new BigDecimal(“1.0”)和new BigDecimal(“1.00”),这个集合就将包含两个元素,因为新增到集合中的两个BigDecimal实例,通过equals方法来比较的时候是不相等的。然而如果你使用TreeSet而不是HashSet来执行同样的过程,集合中将只包含一个元素,因为这两个BigDecimal实例在通过compareTo方法进行比较的时候是相等的。

编写compareTo方法与编写equals方法非常相似,但也存在几处重大的差别。因为Compareable接口时参数化的,而且comparable方法是静态的类型,因此不必进行类型检查,也不需要对它的参数进行类型的转换。如果参数的类型不合适,这个调用甚至无法编译。如果参数为null,这个调用。如果参数为null,这个调用应该抛出NullPointerException异常,并且一旦该方法试图访问它的成员变量时就应该抛出。

CompareTo方法中的域的比较是顺序的比较,而不是同等性的比较。比较对象引用域可以是通过递归地调用compareTo方法来实现。如果一个域并没有实现Compareable接口,或者你需要使用一个非标准的排序关系,就可以使用一个显示的Comparator来代替。或者是编写自己的Comparator,或者是使用已有的Comparator,例如(第八条没有实现这个方法,此例比较方法是jdk中的方法)针对下面的这个类,已经有一个compareTo方法:

public final class CaseInsensitiveString implements Comparable {

public int comparaTo(CaseInsensitiveString cis) {

return String.CASE_INSENSITIVE_ORDER.compare(s, cis.s);

}

... //Remainder omitted

}

比较整数型基本类型的域,可以使用关系操作符<和>。例如,浮点域用Double.compare或者Float.compare,而不同关系操作符,当应用到浮点值得时候,它们没有遵守compareTo的通用约定。对于数组域,则要把这些知道原则应用到每个元素上。

如果一个类有多个关键域,那么比较这些关键域的顺序非常关键。必须从最关键的域开始,逐步进行到所有的重要域。如果某个域的比较产生了非零的结果(0代表着相等),则整个比较操作结束,并返回该结果。如果最关键的域是相等的,则再比较下一个关键域,以此类推,如果所有域都是相等的,那么才返回0。例如下面的例子:

public final class PhoneNumber implements Comparable {

private final short areaCode;

private final short prefix;

private final short lineNumber;

public PhoneNumber(int areaCode, int prefix,

int lineNumber) {

this.areaCode = (short) areaCode;

this.prefix = (short) prefix;

this.lineNumber = (short) lineNumber;

}

@Override

public int compareTo(PhoneNumber pn) {

if (areaCode < pn.areaCode)

return -1;

if(areaCode > pn.areaCode)

return 1;

if (prefix < pn.prefix)

return -1;

if (prefix > pn.prefix)

return 1;

if (lineNumber < pn.lineNumber)

return -1;

if (lineNumber > pn.lineNumber)

return 1;

return 0;

}

}

虽然这个方法可行,但它还可以进行改进。先来看看compareTo方法的约定,并没有指定返回值的大小(magnitude),而只是指定了返回值的符号。可以利用这一点来简化代码,或许还可以提高它的运行速度:

public int compareTo(PhoneNumber pn) {

int areaCodeDiff = areaCode - pn.areaCode;

if (areaCodeDiff != 0)

return areaCodeDiff;

int prefixDiff = prefix - pn.prefix;

if (0 != prefixDiff)

return prefixDiff;

return lineNumber - pn.lineNumber;

}

使用这种方法的时候需要注意,有符号的32位整数还不足以大到能够表达任意两个32位整数的差值,如果i是一个很大的正整数,j是一个很小的负整数,i-j有可能会溢出,并且返回一个负值= 。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容