Effective Java读书笔记-考虑实现Comparable接口

来源:互联网 发布:知乎 最美av神作 编辑:程序博客网 时间:2024/04/28 10:02

compareTo方法没有在Object中声明,相反它是Comparable接口的唯一方法。compareTo方法不单单可以进行简单的同性比较,而且允许执行顺序比较。类实现了Comparable接口就表明它的实例具有内在的排序关系(natural ordering)。实现Comparable接口的对象数组进行排序的操作很简单:

Array.sort(a);

一旦类实现了Comparable接口,它就可以跟许多泛型算法(generic algorithm)以及依赖于该接口的的集合实现(collection implementation)进行协作。如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母排序,按数值排序或者按年代排序,那你就应该坚决考虑实现这个接口。

public interface Comparable<T>{    int compareTo(T t);}

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

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

  • 实现者必须满足所有的x和y都满足sgn(x.comapreTo(y)) ==
    -sgn(y.compareTo(x))。(这也暗示着,当且仅当y.compareTo(x)抛出异常时,x.compareTo(y)才能抛出异常。)
  • 实现者还必须确保这个比较关系是可传递的:(x.compareTo(y)>0&&y.compareTo(z)>0)暗示着x.compareTo(z)>0.
  • 实现者需要确保x.compareTo(y) == 0暗示着所有的z都满足sgn(x.compareTo(z)) == sgn(y.compareTo(z)).
  • 强烈建议((x.compareTo(y))==0) == (x.equals(y)),但这并非绝对必要。一般来说,任何实现了Comparable接口的类,如果违反了这个条件,都应该予以明确的说明。推荐使用这样的说法:“注意,该类具有内在排序的功能,但是与equals不一致。”

与equals不同的是,在跨越不同的类时,compareTo可以不用做比较:如果两个被比较的对象引用不同类的对象,compareTo可以抛出ClassCastException异常。由compareTo方法施加的等同性测试(equality test),也一定遵守相同与equals约定所施加的限制条件:自反性、对称性和传递性。

编写compareTo方法与编写equals方法非常的类似,但是也存在着几处重大的差别。因为Comaprable接口是参数化的,而且comaprable方法是静态类型,因此不必进行类型检查,也不必对其参数进行类型转换。如果参数的类型不合适,这个调用甚至无法编译。如果参数为null,这个调用应该抛出NullPointerException异常,并且一旦该方法试图访问它的成员时就应该抛出。compareTo方法中域的比较是顺序比较,而不是等同性比较。如果一个域并没有实现Comparable接口,或者你需要使用一个非标准的排序关系,就可以使用一个显式的Comparator来代替。或者编写自己的Comparator,或者使用已有的Comparator。例如:

public final class CaseInsensitiveString implements Comparable<CaseInsensitiveString >{    public int compareTo(CaseInsensitiveString cis){        return String.CASE_INSENSITIVE_ORDER.compare(s,cis.s);    }}

注意:CaseInsensitiveString 类实现了Comparable< CaseInsensitiveString>接口。由此可见,CaseInsensitiveString引用只能与其他的Comparable< CaseInsensitiveString>引用进行比较。在声明类中实现Comparable接口时,这是常用的模式。还需要注意compareTo方法的参数是CaseInsensitiveString,而不是Object。这是上述的类声明所要求的。

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

如果一个类中有多个关键的域,那么,按什么样的顺序来比较这些域是非常关键的,你必须从最关键的域开始比较,逐步进行到所有的重要域。如果某个域的比较产生了非零的结果,则整个比较操作结束,并返回该结果。如果关键的域比较是相等的则比较次关键的域,以此类推,直到所有的域都是相等的,则对象是相等的,并返回零。例如:

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(prefixDiff != 0){        return prefixDiff ;    }    return lineNumber - pn.lineNumber;}

注意:这项技巧能够工作的很好,但是用起来要非常的小心。除非你能否确保相关的域不为负值,或者更一般的情况:最小和最大的可能域值之差小于或者等于INTEGER.MAX_VALUE,否则就不要使用这种方法。如果i是一个很大的正整数,而j是一个很大的负整数,那么i-j可能会溢出,并返回一个负值。这就使得compareTo方法对某些参数返回错误的结果。

原创粉丝点击