考虑实现Comparable接口

  compareTo方法没有在Object中声明。相反,它是Comparable接口中唯一的方法。compareTo方法不但允许进行简单的等同性比较,而且允许执行顺序比较,除此之外,它与Object的equals方法具有相似的特征,它还是个泛型。类实现了Comparable接口的对象数组进行排序那么简单:

Arrays.sort(a);

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

public class WoedList{
public static void main(String[] args){
Set<String> s = new TreeSet<String>();
Collections.addAll(s,args);
System.out.println(s);
}
}

  一旦实现了Comparable接口,他就可以跟许多泛型算法(generic algorithm)以及依赖于该接口的集合实现(collection implementation)进行协作。你付出很小的努力就可以获得非常强大的功能。实际上,Java平台类库中的返回值类都实现了Comparable接口。如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母顺序。按数值顺序或者按年代排序,那你就应该考虑实现这个接口:

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

  compareTo方法的通用约定与equals方法的相似:

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

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

  • 实现者必须确保所有的x和y都满足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。(这也暗示着,当且仅当y.compareTo(x)抛出异常时,x.compareTo(y)才必须抛出异常。)
  • 实现者还必须确保这个比较关系是可传递的:(x.compareTo(y) > 0 && y.compareTo(z) > 0 暗示着x.compreTo(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约定。与equals不同的是,在跨域不同类的时候,compareTo可以不做比较:如果两个别比较的对象引用不同的类的对象,compareTo可以抛出ClassCastException异常。通常,这正是compareTo在这种情况下该做的事情,如果类设置了正确的参数,这也正是它要做的事情。虽然以上约定中并没有把跨域之间的比较排除之外,但是从Java 1.6发行版本开始,Java平台类库中就没有哪个类有支持这种特性了。

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

  现在我们来回顾一下compareTo约定中的条款。第一条指出,如果颠覆了两个对象引用之间的比较方向,就会发生下面的情况:如果第一个对象小于第二个对象,则第二个对象一定大于第一个对象;如果第一个对象等于第二个对象,则第二个对象一定等于第一个对象;如果第一个对象大于第二个对象,第二个对象一定小于第一个对象。第二条指出,如果一个对象大于第二个对象,并且第二个对象又大于第三个对象,那么第一个对象一定大于第三个对象。最后一条指出,在比较时被认为相等的所有对象。它们跟别的对象做比较时一定会产生同样的结果。

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

  compareTo约定的最后一个是一个强烈的建议,而不是真正的规则,只是说明了compareTo方法施加的等同性测试,在通常情况下应该返回与equals方法相同的的结果。如果遵守了这一条,那么由compareTo方法所施加的顺序关系被认为“与equals一致(consistent with equals)”。如果违反了这一条规则,顺序关系就被认为“域equals不一致(inconsistent with equals)”。如果一个类的compareTo方法施加了一个与equals方法不一致的顺序关系,它仍然能够正常工作,但是,如果一个有序集合(corted collection)包含了该类的元素,这个集合就可能无法遵守相应集合接口(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方法非常相似,但也存在几处重大差别。因为Comparable接口是参数化的,而且comparebale方法是静态的类型,因此不必进行类型检查,也不必对它的参数进行类型转。如果参数的类型不合适,这个调用甚至无法编译。如果参数为null,这个调用应该抛出NullPointException异常,并且一旦该方法试图访问它的成员时就应该抛出。

  CompareTo方法中域的比较是顺序的比较,而不是等同性的比较。比较对象引用域可以是通过递归地调用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)
}
... //Remainder omitted
}

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

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

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

public int compareTo(PhoneNumber pn){
//Compare area codes
if(areaCode < pn.areaCode)
return -1;
if(areaCode > pn.areaCode)
return 1;
//Area codes are equal,compare prefixes
if(prefix < pn.prefix)
return -1;
if(prefix > pn.prefix)
return 1;
//Area codes and prefixes are equal, compare line numbers
if(lineNumber < pn.lineNumber)
return -1;
if(lineNumber > pn.lineNumber)
return 1;
return 0;
}

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

public int compareTo(PhoneNumber pn){
//Compare area codes
int areaCodeDiff = areaCode - pn.areaCode;
if(areaCode!=0)
return areaCodeDiff;
//Area codes are equal,compare prefixes
int prefixDiff = prefix - pn.prefix;
if(prefixDiff != 0)
return prefixDiff;
//Area codes and prefixes are equal ,compare line numbers
return lineNuber - pn.lineNuber;
}

  这项技巧在这里能工作的很好,但是用起来要非常小心。除非你确信相关的域不会为负值,或者一般的情况:最小和最大的可能域值之差小于或者等于INTEGER.MAX_VALUE(2^31 -1),否则就不要使用这种方法。这项技巧有时不能正常工作的原因在于,一个有符号的32位的整数还没有大到足以表达任意两个32为整数的差。如果i是一个很大的正整数(int类型),而j是一个很大的负整数(int类型),那么(i-j)将会溢出,并且返回一个负值。这样就使得compareTo方法将对某些参数返回错误的结果,违反了compareTo约定的第一和第二条。这不是一个纯粹的理论问题:他已经在实际的系统中导致了失败。这些失败可能非常难以调试,因为这样的compareTo方法对大多数的输入值都能够正常工作。

最新文章

  1. Python中获取当前日期的格式
  2. jQuery管理包装集笔记
  3. HTML标签用法
  4. poj1556The Doors
  5. UVa 11714 - Blind Sorting
  6. Linux 文件的几种类型
  7. 1.Getting Started
  8. 数据结构之网络流入门(Network Flow)简单小节
  9. ubuntu 15.10 安装jdk
  10. HashMap、Hashtable、 LinkedHashMap、TreeMap四者之分。
  11. SM4密码算法(附源码)
  12. python2x和python3的区别
  13. repo
  14. 支持markwon写ppt的工具marp-调研
  15. python全栈开发day80--评论楼、评论树
  16. P1002 过河卒
  17. WIN7 环境下搭建 PHP7(64 位)操作步骤
  18. solr基础使用概述
  19. python2.7
  20. mark DOwm

热门文章

  1. java socket InputStream和OutputStream
  2. SEO大师分析的八条
  3. Java类加载器( 死磕 4)
  4. Linux系统上安装字体
  5. poj3295 Tautology —— 构造法
  6. Codeforces Round #376 (Div. 2) F. Video Cards —— 前缀和 & 后缀和
  7. [Vim 使用]vim 自动括号补全配置
  8. session机制大揭秘(结合cookie)
  9. 第九章-IO编程
  10. kamailio/opensips snmp/cacti/zabbix监控