Java性能优化(11):考虑实现Comparable接口

一个类实现了Comprable接口,就表明它的实例具有内在的排序关系。若一个数组中的对象实现了Comparable接口,则对这个数组进行排序非常简单:

Arrays.sort(a);

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

public class WordList
{
    public static void main(String[] args)
    {
        Set s = new TreeSet();
        s.addAll(Arrays.asList(args));
        System.out.println(s);
    }
}

一旦你的类实现了Comparable接口,它就可以跟许多泛型算法,以及依赖于该接口集合实现进行协作。事实上,Java平台中所有值类都实现了Comparable。如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母表顺序、按数值顺序或者年代顺序,那么你总是应该考虑实现这个接口。

CompareTo方法的通用约定与equals方法的通用约定具有相似的特征,下面是它的内容。

将当前这个对象与指定对象进行顺序比较。当该对象小于、等于或大于指定对象的时候,分别返回一个负整数、零或者正整数。如果由于指定对象的类型使得无法进行比较,则抛出ClassCastException异常。

在下面的说明中,记号表示数学上的signum函数,它根据expresssion的值为负值、零和正值,分别返回-1、0或1.

实现者必须保证对于所有的x和y,满足sgn(x.compareTo(y))=-sgn(y.compareTo(x))。

  • 实现者也必须保证这个比较关心是可传递的:(x.compareTo(y))>0&&y.compareTo(z)>0暗示着x.compareTo(z)>0。

  • 最后,实现者保证x.compareTo(y)==0 暗示着:对于所有的z,sgn(x.comrpareTo(z))==sgn(y.compareTo(z))。

  • 强烈建议(x.compareTo(y)==0)==(x.equals(y)),但这不是严格要求。一般而言,任何实现了Comparable接口的类,若违反了这个条件,应该明确予以说明。推荐使用这样的说法:”注意:该类具有内在排序功能,但是与equals不一致。”

请不要对上述约定中的数学关系感到厌烦。如同equals约定一样,compareTo约定并没有它看起来那么复杂。在一个类的内部,任何一种合理的顺序关系都可以满足compareTo约定。然而,与equals不同的是,在跨越不同类的时候,compareTo可以不做比较:如果两个被比较对象引用分别指向不同类的对象,那么compareTo可以抛出ClassCastException异常。通常,这正是compareTo在这种情况下应该做的事情。

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

域的本身是顺序比较,而不是相等比较。比较对象引用域可以通过递归地调用compareTo方法来实现。如果一个域斌没有实现Comparable接口,或者你需要使用一个域并没有实现Comparator。或者编写专门的Comparator。或者使用已有的Comparator,其compareTo方法使用一个已有的Comparator:

public int compareTo(Object o)
{
    CaseInsensitiveString cis = (CaseInsensitiveString)o;
    return String.CASE_INSENSITIVE_ODER.compare(s, cis.s);
}

比较原语类型的域,你可以使用关系操作符<和>;比较数组域是,你可以把这些指导原则应用到每一个元素上。如果一个类有多个关键域,那么,按什么样的顺序来比较这些域是非常关键的。你必须从最关键的域开始,逐步进行到所有的重要域。如果有一个域的比较产生了非零的结果,则整个比较操作结束,并返回该结果。如果最关键的域是相等的,则进一步比较次的最关键的域,以此类推。如果所有域都是相等的,则对象是相等的,返回零。

public int compareTo(Object o) {
        PhoneNumber pn = (PhoneNumber) o;
        if (areaCode < pn.areaCode)
            return -1;
        if (areaCode > pn.areaCode)
            return 1;
        if (exchange < pn.exchange)
            return -1;
        if (exchange > pn.exchange)
            return 1;
        if (extension < pn.extension)
            return -1;
        if (extension > pn.extension)
            return 1;
        return 0;
    }

虽然这个可以工作的很好,但它还有改进的余地。回想一下,compareTo方法的约定并没有指定返回值的大小,而是指定了返回值的符号。你可以利用这一点来简化代码,提高运行速度。

public int compareTo(Object o) {
        PhoneNumber pn = (PhoneNumber) o;
        int areaCodeDiff=areaCode-pn.areaCode;
        if(areaCodeDiff!=0)
            return areaCodeDiff;
        int exchangeDiff=exchange-pn.exchange;
        if(exchangeDiff!=0)
            return exchangeDiff;
        return extension-pn.extension;
    }

要谨慎使用这项技巧,除非你确信问题中的域不会是负的,或者更一般的情况,最小和最大的可能域值之差小于或者等于INTEGER.MAX_VALUE(2,-1),否则就不要使用这种方法。这项技巧往往不能正常工作的原因在于,一个有符号的32位整数还没有大到足以表达任意两个32位的差。如果i是一个很大的正整数,而j是一个绝对值很大的负整数,,那么(i-j)将会溢出,并返回一个负值。这就使得compareTo不能工作。对于某些实参,它会返回无意义的结果。

posted on 2015-09-06 15:26  爱你一万年123  阅读(378)  评论(0编辑  收藏  举报

导航