转载自: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. solidity 十六进制字符串转十六进制bytes

    pragma solidity ^0.4.16; contract Metadata { // 十六进制字符串转换成bytes function hexStr2bytes(string data)re ...

  2. Juice账号

    zhangxiaocong69 zxc6545398 15657167502 区块链账户: 0x00680404766965143796a0a070835c3cdf9a4a50

  3. vs2008 c#项目调试dll源码,问题:“若要调试此模块,请将其项目生成配置更改为“调试”模式” 的解决方案

    情况: 1:有程序 Trans.exe 的vs2008 c#源码:Trans.exe项目里引用了 Water.dll: 2:有Water.dll的项目源码: 3:想在Trans.exe里调试Water ...

  4. 压力测试工具-webbench

    简述 偶然情况下看到一款性能测试工具webbench,看着挺不错的记录一下安装过程,在以后项目上线过程中可以压一压一些页面的并发情况,对项目性能有个大致的了解. 原理 webbench首先fork出多 ...

  5. 4.安装hive

      下载安装包并解压安装元数据库配置hive添加hvie环境变量修改hive-env.sh修改hive配置文件初始化metastore使用hive cli配置hivemestore配置hiveserv ...

  6. 软工第三次作业——个人PSP

    9.22--9.26本周例行报告 1.PSP(personal software process )个人软件过程. 类型 任务 预计时间 开始时间 结束时间 中断时间 实际用时 准备工作 学习重定向 ...

  7. 基于NABCD评论“探路者”Alpha版作品

    1.分析 N(Need):”为了重温贪吃蛇这一经典游戏,本组的选题定为贪吃蛇游戏,并在此基础上进行了新的创新,将普通的贪吃蛇游戏改为单词版贪吃蛇.市面上的英语单词背记软件对于那些缺少英语学习兴趣.毅力 ...

  8. PHPCMS调取当前栏目的描述、文章位置导航、当前栏目链接、当前栏目名称

    当我们填写了栏目描述,怎么调用出来. 使用 {$CATEGORYS[$catid][description]} 就可以把栏目的描述调用出来 下面三个也比较常用{catpos($catid)} 显示文章 ...

  9. JavaScript初探系列之面向对象

    面向对象的语言有一个标志,即拥有类的概念,抽象实例对象的公共属性与方法,基于类可以创建任意多个实例对象,一般具有封装.继承.多态的特性!但JS中对象与纯面向对象语言中的对象是不同的,ECMA标准定义J ...

  10. 将代码上传到GitHub

    网上看了很多资料,都是用的命令行,比较难看懂,自己摸索了一下怎么样在图形界面上操作.下面记录的只是简单的如何把本地仓库直接上传到服务器上. 在mac上下载个GitHub Mac客户端,安装好后运行,输 ...