转载自: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. [堆+贪心]牛客练习赛40-B

    传送门:牛客练习赛40 题面: 小A手头有 n 份任务,他可以以任意顺序完成这些任务,只有完成当前的任务后,他才能做下一个任务 第 i 个任务需要花费  x_i 的时间,同时完成第 i 个任务的时间不 ...

  2. linux 文件已经删除,但是空间没有释放的原因

    监控系统报告一台服务器的空间满了,登陆后发现/tmp下有大量access_log文件,分析是Apache的日志文件很久没有清理了,确认并执行删除操作. 但是,问题来了,执行 rm /tmp/acces ...

  3. HADOOP docker(四):安装hive

    1.hive简介2.安装hive2.1 环境准备2.1.1 下载安装包2.1.2 设置hive用户的环境变量2.1.3 hive服务端配置文件2.1.4 hive客户端配置文件2.1.4 分发hive ...

  4. windows编程入门最重要的

    要入门 Windows 编程,最重要的不是阅读什么教材,使用什么工具,而是先必须把以下几个对于初学者来说非常容易困惑的重要概念搞清楚: 1. 文字的编码和字符集.这部分需要掌握 ANSI 模式和 Un ...

  5. android在程序崩溃时Catch异常并处理

    Android系统的"程序异常退出",给应用的用户体验造成不良影响.为了捕获应用运行时异常并给出友好提示,便可继承UncaughtExceptionHandler类来处理.通过Th ...

  6. vs调试时报503错误

    开发中遇到了一个神问题,困扰了几个月没解决. 在本机调试,或者用iis服务器直接指向项目目录,访问网页任何页面都是报503. 一直找不到原因,配置文件也修改了,还是解决不了. 今天20170110一次 ...

  7. Java中的基本数据类型包装类

    在 java 中为什么会有基本数据类型的包装类? ①:基本数据类型之间的相互转换不是都可以制动转换的,而你强制转换又会出问题,比如String类型的转换为int类型的,那么jdk为了方便用户就提供了相 ...

  8. iOS- UIButton/UIImageView/UISlider/UISwitch操作

    如果看不到图片 可以尝试更换浏览器(推荐Safari ) 一.控件的属性 1.CGRect frame 1> 表示控件的位置和尺寸(以父控件的左上角为坐标原点(0, 0)) 2> 修改这个 ...

  9. OSG学习:响应键盘鼠标示例

    示例功能:示例里面有两个模型,一个是牛,另一个是飞机.鼠标右键时牛和飞机都隐藏,鼠标左键双击时牛和飞机都显示,按键盘上面的LEFT键,显示牛,按键盘上面的RIGHT键显示飞机.其中显示与隐藏节点使用的 ...

  10. mysql的my.cnf配置文件

    [client]port = 3306default-character-set=utf8mb4socket = /var/run/mysqld/mysql.sock# user=david# pas ...