转载自:http://my.oschina.net/jack230230/blog/56339

Java的Comparable接口提供一个对实现了这个接口的对象列表进行排序的办法。原始的排序对于简单的对象来说具有意义,但是当我们面对复杂的面向对象的业务逻辑对象时,事情变得复杂的多。从业务经理的角度来看,一些交易对象的自然顺序可能是按照交易的价值来排序的,但是从系统管理员的角度来看,这个排序的规则可能是交易的速度。所以在大多数情况下,并没有明确的业务领域对象的自然排序规则。
    假设我们找到了一个需要排序的类,比如说Campany。我们把公司的offical name作为主关键字,把id作为次要关键字。这个类的实现如下:
01 public class Company implements Comparable<Company>
{
02   
03     private final String
id;
04     private final String
officialName;
05   
06     public Company(final String
id, 
final String
officialName) {
07         this.id
= id;
08         this.officialName
= officialName;
09     }
10   
11     public String
getId() {
12         return id;
13     }
14   
15     public String
getOfficialName() {
16         return officialName;
17     }
18   
19     @Override
20     public int hashCode()
{
21         HashCodeBuilder
builder = 
new HashCodeBuilder(1729);
22         builder.append(this.getId());
23         builder.append(this.getOfficialName());
24         return builder.toHashCode();
25     }
26   
27     @Override
28     public boolean equals(final Object
obj) {
29         if (obj
== 
this)
{
30             return true;
31         }
32         if (!(obj instanceof Company))
{
33             return false;
34         }
35         Company
other = (Company) obj;
36         EqualsBuilder
builder = 
new EqualsBuilder();
37         builder.append(this.getId(),
other.getId());
38         builder.append(this.getOfficialName(),
other.getOfficialName());
39         return builder.isEquals();
40     }
41   
42     @Override
43     public int compareTo(final Company
obj) {
44         CompareToBuilder
builder = 
new CompareToBuilder();
45         builder.append(this.getOfficialName(),
obj.getOfficialName());
46         builder.append(this.getId(),
obj.getId());
47         return builder.toComparison();
48     }
49 }

这个实现看起来没问题,假设现在这个类提供的信息不够使用,我们又创建了这个类的一个子类CompanyDetail类用以扩展他。例如我们想以一个表的形式显示公司的信息,我们就可以用这个类。
01 public class CompanyDetails extends Company
{
02   
03     private final String
marketingName;
04     private final Double
marketValue;
05   
06     public CompanyDetails(final String
id, 
final String
officialName, 
final String
marketingName, 
final Double
marketValue) {
07         super(id,
officialName);
08         this.marketingName
= marketingName;
09         this.marketValue
= marketValue;
10     }
11   
12     public String
getMarketingName() {
13         return marketingName;
14     }
15   
16     public Double
getMarketValue() {
17         return marketValue;
18     }
19   
20     @Override
21     public int hashCode()
{
22         HashCodeBuilder
builder = 
new HashCodeBuilder(1931);
23         builder.appendSuper(super.hashCode());
24         builder.append(this.getMarketingName());
25         return builder.toHashCode();
26     }
27   
28     @Override
29     public boolean equals(final Object
obj) {
30         if (obj
== 
this)
{
31             return true;
32         }
33         if (!(obj instanceof CompanyDetails))
{
34             return false;
35         }
36         CompanyDetails
other = (CompanyDetails) obj;
37         EqualsBuilder
builder = 
new EqualsBuilder();
38         builder.appendSuper(super.equals(obj));
39         builder.append(this.getMarketingName(),
other.getMarketingName());
40         builder.append(this.getMarketValue(),
other.getMarketValue());
41         return builder.isEquals();
42     }
43 }
这个类的实现看起来还是没什么问题,但是事实上是有问题的,我们可以写一个test指出问题在哪里。当我们没有对父类的所有细节加以注意时,问题就来了。
01 CompanyDetails
c1 = 
new CompanyDetails("231412""McDonalds
Ltd"
"McDonalds
food factory"
120000.00);
02 CompanyDetails
c2 = 
new CompanyDetails("231412""McDonalds
Ltd"
"McDonalds
restaurants"
60000.00);
03   
04 Set<CompanyDetails>
set1 = CompaniesFactory.createCompanies1();
05 set1.add(c1);
06 set1.add(c2);
07   
08 Set<CompanyDetails>
set2 = CompaniesFactory.createCompanies2();
09 set2.add(c1);
10 set2.add(c2);
11   
12 Assert.assertEquals(set1.size(),
set2.size());
    我们构造了两个set,但是结果是assert的结果是不相等。这是为什么?其中一个set是一个HashSet,他依赖对象的hashCode()和equals()方法,但是另一个是TreeSet,他只是依赖Comparable接口,而这个接口在子类中我们并没有实现。在领域对象被扩展的时候这是很常见的一个错误,但是更重要的是这是不好的编码约定造成的。我们使用Apache
Commons包中的builder来实现hashCode(),equals().和compareTo()方法。这些builder提供了appendSuper()方法,此方法指示了如何调用这些方法在父类中的实现。如果你看过Joshua Bloch 的Effective Java,你会发现这是错误的。如果我们在子类中添加成员变量,在不违反对称规则的情况下,我们就不能正确的实现equals()方法和compareTo()方法。我们应该使用组合的方式而不是继承。如果我们使用组合的方式构造CompanyDetails,对于Comparable接口来说没有任何问题,因为我们没有自动的实现,而且在默认的情况允许不同的行为。而且我们也能满足正确的equals()和hashCode()的需求。

这篇文章提到的问题非常普遍,但是经常被忽视。Comparable接口的问题实际是由于不好的约定和对使用的接口需求的错误理解造成的。作为一个Java开发人员或架构师,你应该特别注意这样的事情,并遵守良好的编码习惯和做法。
越大的项目,这种问题就越显得重要。这里我总结了一个使用Comparable接口的最佳实践,可以避免这个错误。

Java的Comparable接口的设计和使用的最佳实践:

·了解你需要创建的领域对象,如果对象没有明确的排序规则,请不要实现Comparable接口。
·更多的使用Comparator而不是Comparable,Comparator在更多的业务使用方式时要显得更为实用。
·如果你需要创建依赖Comparable接口的接口或者库,如果可能的话你提供自己的Comparator实现,否则就写一个良好的文档指明在你的接口实现类中如何实现。
·遵守良好的编码习惯和做法。Effective Java是很好的推荐。 

Java的Comparable接口的一个陷阱的更多相关文章

  1. java比较器Comparable接口和Comaprator接口

    Comparable故名思意是比较,意思就是做比较的,然后进行排序. 1.什么是comparable接口 此接口强行对实现它的每个类的对象进行整体排序.此排序被称为该类的自然排序 ,类的 compar ...

  2. java.lang.Comparable接口

    转自:http://blog.csdn.net/zccst/article/details/5092920 java.lang.Comparable 接口 作者: zccst java.lang.Co ...

  3. java实现Comparable接口和Comparator接口,并重写compareTo方法和compare方法

    原文地址https://segmentfault.com/a/1190000005738975 实体类:java.lang.Comparable(接口) + comareTo(重写方法),业务排序类 ...

  4. Java之comparable接口

    comparable 接口: 1. 问题:java.util.Collections 类中的方法 Collections.sort(List list) 是根据什么确定容器中对象的“大小”顺序的? 2 ...

  5. Java之Comparable接口和Comparator接口

    Comparable & Comparator 都是用来实现集合中元素的比较.排序的: Comparable 是在集合内部定义的方法实现的排序: Comparator 是在集合外部实现的排序: ...

  6. Java.lang.Comparable接口和Java.util.Comparator接口的区别

    Java的Comparator和Comparable当需要排序的集合或数组不是单纯的数字型时,通常可以使用Comparator或Comparable,以简单的方式实现对象排序或自定义排序. 1.Com ...

  7. java lang(Comparable接口) 和java util(Comparator接口)分析比较

    //Comparable 接口强行对实现它的每个类的对象进行整体排序. -- 自然排序.类的compareTo称为自然比较方法. public interface Comparable<T> ...

  8. Java中Comparable接口和Comparator接口的简单用法

    对象比较器 1.Comparable接口 此接口强行对实现它的每个类的对象进行整体排序,这种排序成为类的自然排序,类的compareTo方法称为类的自然比较方法. 代码示例 import java.u ...

  9. java通过Comparable接口实现字符串比较大小排序的简单实例

    /** * 对象比较大小compare的用法 字符串排序 * 练习代码, 给定字符串" nba" "cba" "ncaa" "wb ...

随机推荐

  1. 词嵌入向量WordEmbedding

    词嵌入向量WordEmbedding的原理和生成方法   WordEmbedding 词嵌入向量(WordEmbedding)是NLP里面一个重要的概念,我们可以利用WordEmbedding将一个单 ...

  2. C++ 学习笔记之——STL 库 vector

    vector 是一种顺序容器,可以看作是可以改变大小的数组. 就像数组一样,vector 占用连续的内存地址来存储元素,因此可以像数组一样用偏移量来随机访问,但是它的大小可以动态改变,容器会自动处理内 ...

  3. openstack对接VMware浅析

    前言 本文是对openstack对接vmware的浅析,所以本文重点是以下两点: 先了解它的整体架构,搞清楚为什么要用这样的架构: 然后再了解架构中的各个组件,组件提供的主要功能与各个组件之间的交互 ...

  4. POJ 1921 Paper Cut(计算几何の折纸问题)

    Description Still remember those games we played in our childhood? Folding and cutting paper must be ...

  5. poj 3009 (深搜求最短路)

    题目大意就是求在特定规则下的最短路,这个规则包含了消除障碍的操作.用BFS感觉选择消除障碍的时候不同路径会有影响,用DFS比较方便状态的还原(虽然效率比较低),因此这道题目采用DFS来写. 写的第一次 ...

  6. Android蓝牙开发浅谈(转)

    http://www.eoeandroid.com/thread-18993-1-1.html 对于一般的软件开发人员来说,蓝牙是很少用到的,尤其是Android的蓝牙开发,国内的例子很少     A ...

  7. java的参数传递

    1按值传递:传递的是原始值的副本,而不是原始值的内存地址 基本数据类型是传原始值的副本 class Test02 { public static void main(String[] args) { ...

  8. iOS- <项目笔记>iOS6 & iOS7屏幕图片适配

    1.为非视网膜\视网膜屏幕分别准备2份图片,比如: 1> 非视网膜 abc.png 2> 视网膜 abc@2x.png 程序检测视网膜屏到会自动替换@2x 2.程序启动图片 * 程序启动过 ...

  9. 关于FEer发展方向的思考

    今天学习了HTTP权威指南这本书,虽然标题是对FEer发展的思考,不过我打算稍后再说这个议题,先对今天学习的内容做个总结. 首先:原来访问服务器的方式有多重,核心是URI,也就是统一资源定位,按照访问 ...

  10. Python ZKPython 安装

    1.由于python客户端依赖c的客户端所以要先安装c版本的客户端cd zookeeper-3.4.5/src/c./configuremake make install 2.下载python扩展包, ...