转载自: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是很好的推荐。 

最新文章

  1. wkhtmltopdf 将网页转换为PDF和图片
  2. spring事务管理器设计思想(二)
  3. Codeforces Round #341 Div.2 A. Wet Shark and Odd and Even
  4. Linux——常用命令详解
  5. Unity3D 使用脚本来控制 UI 的 Image 显示的图片。
  6. 【Android 界面效果21】Android ViewPager使用详解
  7. Android 图标右上角添加数字提醒
  8. docker四种网络模式
  9. MFC模态对话框的消息循环
  10. python中pygame模块的Linux下安装过程
  11. 详解mybatis配置文件
  12. Go 包管理工具--glide
  13. asp.net上传图片,上传图片
  14. Laravel 5.2数据库--填充数据
  15. Apktool下载与安装 windows环境
  16. gitlab8.2-&gt;8.16-&gt;8.17-&gt;9.0升级
  17. C++学习(三十二)(C语言部分)之 栈
  18. .net从网络接口地址获取json,然后解析成对象(一)
  19. mybatis一对多关联查询——(九)
  20. 二进制GCD算法 减少%的时间消耗

热门文章

  1. Matlab 图象操作函数讲解
  2. C++ 学习笔记之——STL 库 queue
  3. 3D动态人脸识别技术分析——世纪晟人脸识别实现三维人脸建模
  4. Python3 数值类型与运算符
  5. 转战Java~
  6. 普通Java类获取Spring的Bean的方法
  7. shiro控制登陆成功后跳回之前的页面
  8. C语言 内存分配 地址 指针 数组 参数 实例解析
  9. 爬取CVPR 2018过程中遇到的坑
  10. 深入了解View的绘制流程