文档章节

Java的Comparable接口的一个陷阱

王振威
 王振威
发布于 2012/05/06 20:29
字数 1227
阅读 5053
收藏 42
点赞 1
评论 7
     Java的Comparable接口提供一个对实现了这个接口的对象列表进行排序的办法。原始的排序对于简单的对象来说具有意义,但是当我们面对复杂的面向对象的业务逻辑对象时,事情变得复杂的多。从业务经理的角度来看,一些交易对象的自然顺序可能是按照交易的价值来排序的,但是从系统管理员的角度来看,这个排序的规则可能是交易的速度。所以在大多数情况下,并没有明确的业务领域对象的自然排序规则。
    假设我们找到了一个需要排序的类,比如说Campany。我们把公司的offical name作为主关键字,把id作为次要关键字。这个类的实现如下:
public class Company implements Comparable<Company> {
 
    private final String id;
    private final String officialName;
 
    public Company(final String id, final String officialName) {
        this.id = id;
        this.officialName = officialName;
    }
 
    public String getId() {
        return id;
    }
 
    public String getOfficialName() {
        return officialName;
    }
 
    @Override
    public int hashCode() {
        HashCodeBuilder builder = new HashCodeBuilder(17, 29);
        builder.append(this.getId());
        builder.append(this.getOfficialName());
        return builder.toHashCode();
    }
 
    @Override
    public boolean equals(final Object obj) {
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof Company)) {
            return false;
        }
        Company other = (Company) obj;
        EqualsBuilder builder = new EqualsBuilder();
        builder.append(this.getId(), other.getId());
        builder.append(this.getOfficialName(), other.getOfficialName());
        return builder.isEquals();
    }
 
    @Override
    public int compareTo(final Company obj) {
        CompareToBuilder builder = new CompareToBuilder();
        builder.append(this.getOfficialName(), obj.getOfficialName());
        builder.append(this.getId(), obj.getId());
        return builder.toComparison();
    }
}


这个实现看起来没问题,假设现在这个类提供的信息不够使用,我们又创建了这个类的一个子类CompanyDetail类用以扩展他。例如我们想以一个表的形式显示公司的信息,我们就可以用这个类。
public class CompanyDetails extends Company {
 
    private final String marketingName;
    private final Double marketValue;
 
    public CompanyDetails(final String id, final String officialName, final String marketingName, final Double marketValue) {
        super(id, officialName);
        this.marketingName = marketingName;
        this.marketValue = marketValue;
    }
 
    public String getMarketingName() {
        return marketingName;
    }
 
    public Double getMarketValue() {
        return marketValue;
    }
 
    @Override
    public int hashCode() {
        HashCodeBuilder builder = new HashCodeBuilder(19, 31);
        builder.appendSuper(super.hashCode());
        builder.append(this.getMarketingName());
        return builder.toHashCode();
    }
 
    @Override
    public boolean equals(final Object obj) {
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof CompanyDetails)) {
            return false;
        }
        CompanyDetails other = (CompanyDetails) obj;
        EqualsBuilder builder = new EqualsBuilder();
        builder.appendSuper(super.equals(obj));
        builder.append(this.getMarketingName(), other.getMarketingName());
        builder.append(this.getMarketValue(), other.getMarketValue());
        return builder.isEquals();
    }
}
这个类的实现看起来还是没什么问题,但是事实上是有问题的,我们可以写一个test指出问题在哪里。当我们没有对父类的所有细节加以注意时,问题就来了。
CompanyDetails c1 = new CompanyDetails("231412", "McDonalds Ltd", "McDonalds food factory", 120000.00);
CompanyDetails c2 = new CompanyDetails("231412", "McDonalds Ltd", "McDonalds restaurants", 60000.00);
 
Set<CompanyDetails> set1 = CompaniesFactory.createCompanies1();
set1.add(c1);
set1.add(c2);
 
Set<CompanyDetails> set2 = CompaniesFactory.createCompanies2();
set2.add(c1);
set2.add(c2);
 
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是很好的推荐。 

原文链接/ OSChina.NET原创翻译

© 著作权归作者所有

共有 人打赏支持
王振威

王振威

粉丝 502
博文 28
码字总数 20820
作品 0
深圳
程序员
加载中

评论(7)

JFinal
JFinal
博主很有潜力,完全不像新手,加油
刘学炜
刘学炜
whaon
whaon
学习了
yunshao
yunshao
对最佳实践表示认同,有收获就要感谢~
崔钢
崔钢
我觉得,对象设计的越简单越好,越低级越好。领域模型,最好不要使用继承。
徐迎龙
徐迎龙
就是说有些时候,换一种设计方法,用组合类的方式(把原本你打算要继承的超类增加为原子类的一个字段),比用继承更好
zhaoyou
zhaoyou
不太明白,用这个 CompaniesFactory.createCompanies1() ?
直接用HashSet不就得了吗?
java集合入门和深入学习,看这篇就差不多了

集合框架: Java中的集合框架大类可分为Collection和Map;两者的区别: Collection是单列集合;Map是双列集合 Collection中只有Set系列要求元素唯一;Map中键需要唯一,值可以重复 Collecti...

sihailoveyan ⋅ 05/04 ⋅ 0

Google的Guava类库简介(转)

说明:信息虽然有点旧,至少可以先了解个大概。 Guava是一个Google的基于Java的类库集合的扩展项目,包括collections, caching, primitives support, concurrency libraries, common annotat...

easonjim ⋅ 2017/11/01 ⋅ 0

【目录导航】JAVA零基础进阶之路

【JAVA零基础入门系列】(已完结)导航目录 Day1 开发环境搭建 Day2 Java集成开发环境IDEA Day3 Java基本数据类型 Day4 变量与常量 Day5 Java中的运算符 Day6 Java字符串 Day7 Java输入与输出...

MFrank ⋅ 前天 ⋅ 0

Java就业变难了?你需要对自己有点信心

伴随着IT的火热,越来越多的人进入了IT领域,这在进一步推动着IT发展的同时也极大增加了就业压力。伴随着激烈的岗位竞争,越来越多的人开始感叹工作难找,越火的行业越是如此,Java自是首当其...

糖宝_d864 ⋅ 06/08 ⋅ 0

java程序员基础进阶篇,万丈高楼平地起

一.final,finally,finalize 三者区别 Final是一个修饰符: 当final修饰一个变量的时候,变量变成一个常量,它不能被二次赋值 当final修饰的变量为静态变量(即由static修饰)时,必须在声明这...

启示录是真的 ⋅ 05/26 ⋅ 0

大型互联网架构必备技术——性能调优专题

性能调优 深入内核,直击故障 ,拒绝蒙圈 性能优化如何理解 1、性能基准 2、什么是性能优化 3、衡量标准 JVM调优 1、Jvm虚拟机内存剖析 2、垃圾收集器 3、实战调优案例与解决方案 4、Jvm运行...

Java高级架构 ⋅ 04/15 ⋅ 0

RMI:Java中的分布式计算框架

RMI全称是Remote Method Invocation-远程方法调用,Java RMI在JDK1.1中实现的,其威力就体现在它强大的开发分布式网络应用的能力上,是纯Java的网络分布式应用系统的核心解决方案之一。其实...

qq_39521554 ⋅ 05/15 ⋅ 0

Java编程基础知识点和技术点归纳

Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互...

Java小辰 ⋅ 05/23 ⋅ 0

Lambda 表达式有何用处?如何使用?(针对Java8)

什么是Lambda? 我们知道,对于一个Java变量,我们可以赋给其一个“值”。 如果你想把“一块代码”赋给一个Java变量,应该怎么做呢? 比如,我想把右边那块代码,赋给一个叫做aBlockOfCode的J...

亭子happy ⋅ 06/06 ⋅ 0

java编程新手入门学习的基础语法

Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互...

Java小辰 ⋅ 05/28 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

一篇文章学懂Shell脚本

Shell脚本,就是利用Shell的命令解释的功能,对一个纯文本的文件进行解析,然后执行这些功能,也可以说Shell脚本就是一系列命令的集合。 Shell可以直接使用在win/Unix/Linux上面,并且可以调用...

Jake_xun ⋅ 22分钟前 ⋅ 0

大数据工程师需要精通算法吗,要达到一个什么程度呢?

机器学习是人工智能的一个重要分支,而机器学习下最重要的就是算法,本文讲述归纳了入门级的几个机器学习算法,加大数据学习群:716581014一起加入AI技术大本营。 1、监督学习算法 这个算法由...

董黎明 ⋅ 54分钟前 ⋅ 0

Kylin 对维度表的的要求

1.要具有数据一致性,主键值必须是唯一的;Kylin 会进行检查,如果有两行的主键值相同则会报错。 2.维度表越小越好,因为 Kylin 会将维度表加载到内存中供查询;过大的表不适合作为维度表,默...

无精疯 ⋅ 58分钟前 ⋅ 0

58到家数据库30条军规解读

军规适用场景:并发量大、数据量大的互联网业务 军规:介绍内容 解读:讲解原因,解读比军规更重要 一、基础规范 (1)必须使用InnoDB存储引擎 解读:支持事务、行级锁、并发性能更好、CPU及...

kim_o ⋅ 今天 ⋅ 0

代码注释中顺序更改 文件读写换行

`package ssh; import com.xxx.common.log.LogFactory; import com.xxx.common.log.LoggerUtil; import org.apache.commons.lang3.StringUtils; import java.io.*; public class DirErgodic ......

林伟琨 ⋅ 今天 ⋅ 0

linux实用操作命令

参考 http://blog.csdn.net/qwe6112071/article/details/50806734 ls [选项] [目录名 | 列出相关目录下的所有目录和文件 -a 列出包括.a开头的隐藏文件的所有文件-A 同-a,但不列出"."和"...

简心 ⋅ 今天 ⋅ 0

preg_match处理中文符号 url编码方法

之前想过直接用符号来替换,但失败了,或者用其他方式,但有有些复杂,这个是一个新的思路,亲测可用 <?php$str='637朗逸·超速新风王(300)(白光)'; $str=iconv("UTF-8","GBK",$s...

大灰狼wow ⋅ 今天 ⋅ 0

DevOps 资讯 | PostgreSQL 的时代到来了吗 ?

PostgreSQL是对象-关系型数据库,BSD 许可证。拼读为"post-gress-Q-L"。 作者: Tony Baer 原文: Has the time finally come for PostgreSQL?(有删节) 近30年来 PostgreSQL 无疑是您从未听...

RiboseYim ⋅ 今天 ⋅ 0

github太慢

1:用浏览器访问 IPAddress.com or http://tool.chinaz.com 使用 IP Lookup 工具获得github.com和github.global.ssl.fastly.net域名的ip地址 2:/etc/hosts文件中添加如下格式(IP最好自己查一...

whoisliang ⋅ 今天 ⋅ 0

非阻塞同步之 CAS

为解决线程安全问题,互斥同步相当于以时间换空间。多线程情况下,只有一个线程可以访问同步代码。这种同步也叫阻塞同步(Blocking Synchronization). 这种同步属于一种悲观并发策略。认为只...

长安一梦 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部