考虑实现Comparable接口
考虑实现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方法对大多数的输入值都能够正常工作。
考虑实现Comparable接口的更多相关文章
- 12.Java中Comparable接口,Readable接口和Iterable接口
1.Comparable接口 说明:可比较(可排序的) 例子:按照MyClass的y属性进行生序排序 class MyClass implements Comparable<MyClass> ...
- java中的Comparable接口
类对象之间比较"大小"往往是很有用的操作,比如让对象数组排序时,就需要依赖比较操作.对于不同的类有不同的语义.如Student类,比较2个学生对象可以比较他们的score分数来评判 ...
- comparator接口与Comparable接口的区别
1. Comparator 和 Comparable 相同的地方 他们都是java的一个接口, 并且是用来对自定义的class比较大小的, 什么是自定义class: 如 public class Pe ...
- Comparable接口与Comparator接口的区别
1. Comparator 和 Comparable 相同的地方 他们都是java的一个接口, 并且是用来对自定义的class比较大小的, 什么是自定义class: 如 public class Pe ...
- Comparable接口
java.util.Arrays类也可以对Object数组进行排序,但是要使用这种方法排序必须实现Comparable接口,此接口就是用于指定对象排序规则的. 设计一个学生类,成绩由高到低排序,成绩相 ...
- Java集合中Comparator和Comparable接口的使用
在Java集合中,如果要比较引用类型泛型的List,我们使用Comparator和Comparable两个接口. Comparable接口 -- 默认比较规则,可比较的 实现该接口表示:这个类的实例可 ...
- Java中的Comparable接口和Comparator接口
Comparator位于包java.util下,比较器,是在集合外部定义排序.Comparable位于包java.lang下,代表当前对象可比较的,是在集合内部实现排序. Comparable代表一个 ...
- C#实现Comparable接口实现排序
C#中,实现排序的方法有两种,即实现Comparable或Comparer接口,下面简单介绍实现Comparable接口实现排序功能. 实现Comparable接口需要实现CompareTo(obje ...
- Java之Comparable接口和Comparator接口
Comparable & Comparator 都是用来实现集合中元素的比较.排序的: Comparable 是在集合内部定义的方法实现的排序: Comparator 是在集合外部实现的排序: ...
- 第12条:考虑实现Comparable接口
CompareTo方法没有在Object中声明,它是Comparable接口中的唯一的方法,不但允许进行简单的等同性比较,而且允许执行顺序比较.类实现了Comparable接口,就表明它的实例具有内在 ...
随机推荐
- 在c代码中获取用户环境变量
1 extern char ** environ 这是一个字符串数组,最后一个元素是null,即\0. 2 在代码中的使用方法 直接extern char **environ,然后 直接environ ...
- 基于地理位置信息的traceroute
我们在机房选择.測试网络的质量的时候,往往仅仅依据跳数.延迟.抖动.网络吞吐量等指标来衡量,非常多时候跳数并不能全然显示网络拓扑优劣,于是写了个traceroute结合whois的小脚本来直观显示每一 ...
- ubuntu搜狗拼音安装
1.官方下载deb 2.双击安装 3.终端im-config,选择fcitx 4.重启 5.输入法设置中add一下sougoupinyin
- Redis使用经验之谈
应用场景 保存用户喜欢的商品信息. 类型: Hash, key: usr:${type_id}:${version_id}:${user_id}:${warehouse_id}, field: ${s ...
- BZOJ 1621 [Usaco2008 Open]Roads Around The Farm分岔路口:分治 递归
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1621 题意: 约翰的N(1≤N≤1,000,000,000)只奶牛要出发去探索牧场四周的土 ...
- multi_socket
threading_test.py #threading #为什么在命令行可以执行,F5不能执行 #线程处理能导致同步问题 from socketserver import TCPServer,Thr ...
- cuda 版本查阅
查看cuda版本 cat /usr/local/cuda/version.txt nvcc -V
- asio 中strand的作用
namespace { // strand提供串行执行, 能够保证线程安全, 同时被post或dispatch的方法, 不会被并发的执行. // io_service不能保证线程安全 boost::a ...
- MongoDB分析工具之三:db.currentOp()
db.currentOp() db.currentOp是个好东西,顾名思义,就是当前的操作.在mongodb中可以查看当前数据库上此刻的操作语句信息,包括insert/query/update/rem ...
- libvirt kvm云主机监控
libvirt