文档章节

Java的Comparable接口的一个陷阱

王振威
 王振威
发布于 2012/05/06 20:29
字数 1227
阅读 5064
收藏 43
     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原创翻译

© 著作权归作者所有

共有 人打赏支持
王振威

王振威

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

评论(7)

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

什么是 Type Class ? Type Class (类型类) 的概念来自 Haskell,表示一系列函数的集合,在概念上, Type Class 和面向对象领域的泛型接口比较类似。 由于 Haskell 是一门纯函数式编程语言,...

joymufeng
10/07
0
0
java 通配符的应用— java 排序算法

这几天无聊,又重新学起java的排序算法,为DualPivotQuickSort做准备。为了更好地适应各种情况,我们选择使用通用类型T和通配符的上下界来实现,同时这次谈的是对数组对象的排序。如果你对j...

天地一MADAO_
2014/03/02
0
0
Android ThreadLocal+PriorityQueue构建多线程队列

一、消息队列 Android中使用了很多消息队列,如Intent,Handler,BroadcastReceiver等。使用消息队列的目的是防止线程阻塞并且将不能及时执行的任务缓存起来,从而提高系统的运行效率。 为了使...

IamOkay
2014/11/03
0
0
Comparator与Comparable的应用

当需要排序的集合或数组不是单纯的数字型时,通常可以使用Comparator或Comparable,以简单的方式实现对象排序或自定义排序。 阅读过程中有任何问题,请联系egg: 邮箱:xtfggef@gmail.com 微...

mrliuze
2015/08/04
0
0
Java学习资料-Comparable和Comparator实现对象比较

当需要排序的集合或数组不是单纯的数字型时,通常可以使用Comparator或Comparable,以简单的方式实现对象排序或自定义排序。下面通过两个例子分别用Comparable和Comparator实现对User对象中年...

晓阳
2015/01/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

好用的vue组件

http://elickzhao.github.io/2017/08/vue%E4%B8%80%E4%BA%9B%E7%89%B9%E5%88%AB%E6%9C%89%E7%94%A8%E7%9A%84%E6%8F%92%E4%BB%B6/...

Littlebox
29分钟前
2
0
linux 源码安装mysql8

1.安装依赖 yum -y install wget cmake gcc gcc-c++ ncurses ncurses-devel libaio-devel openssl openssl-devel   2.下载源码包 wget https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-......

苏牧影子
29分钟前
1
0
BeanFactory和FactoryBean

BeanFactory BeanFactory是ioc容器的顶层接口,里面定义了一些容器基本的功能 类似ConfigurableBeanFatory和ApplicationContext就是比较高级的容器,除了基本的方法之外,还实现了很多高级的...

sendo
30分钟前
1
0
Java并发(9)- 从同步容器到并发容器

引言 容器是Java基础类库中使用频率最高的一部分,Java集合包中提供了大量的容器类来帮组我们简化开发,我前面的文章中对Java集合包中的关键容器进行过一个系列的分析,但这些集合类都是非线...

Ala6
35分钟前
3
0
Java定时器Timer学习之一

种类: 接通延时型定时器:接通延时型定时器是各种PLC(可编程控制器)中最常见最基本的定时器,这种定时器在Siemens的PLC中,成为SD型定时器 断开延时型定时器:这种定时器是当输入条件00000为ON时...

王怀楼
37分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部