转载自:http://my.oschina.net/jack230230/blog/56339
Java的Comparable接口提供一个对实现了这个接口的对象列表进行排序的办法。原始的排序对于简单的对象来说具有意义,但是当我们面对复杂的面向对象的业务逻辑对象时,事情变得复杂的多。从业务经理的角度来看,一些交易对象的自然顺序可能是按照交易的价值来排序的,但是从系统管理员的角度来看,这个排序的规则可能是交易的速度。所以在大多数情况下,并没有明确的业务领域对象的自然排序规则。
假设我们找到了一个需要排序的类,比如说Campany。我们把公司的offical name作为主关键字,把id作为次要关键字。这个类的实现如下:
01 |
public class Company implements Comparable<Company>
{ |
03 |
private final String
id; |
04 |
private final String
officialName; |
06 |
public Company(final String
id, final String
officialName) { |
08 |
this.officialName
= officialName; |
11 |
public String
getId() { |
15 |
public String
getOfficialName() { |
20 |
public int hashCode()
{ |
21 |
HashCodeBuilder
builder = new HashCodeBuilder(17, 29); |
22 |
builder.append(this.getId()); |
23 |
builder.append(this.getOfficialName()); |
24 |
return builder.toHashCode(); |
28 |
public boolean equals(final Object
obj) { |
32 |
if (!(obj instanceof Company))
{ |
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(); |
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(); |
这个实现看起来没问题,假设现在这个类提供的信息不够使用,我们又创建了这个类的一个子类CompanyDetail类用以扩展他。例如我们想以一个表的形式显示公司的信息,我们就可以用这个类。
01 |
public class CompanyDetails extends Company
{ |
03 |
private final String
marketingName; |
04 |
private final Double
marketValue; |
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; |
12 |
public String
getMarketingName() { |
16 |
public Double
getMarketValue() { |
21 |
public int hashCode()
{ |
22 |
HashCodeBuilder
builder = new HashCodeBuilder(19, 31); |
23 |
builder.appendSuper(super.hashCode()); |
24 |
builder.append(this.getMarketingName()); |
25 |
return builder.toHashCode(); |
29 |
public boolean equals(final Object
obj) { |
33 |
if (!(obj instanceof CompanyDetails))
{ |
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(); |
这个类的实现看起来还是没什么问题,但是事实上是有问题的,我们可以写一个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); |
04 |
Set<CompanyDetails>
set1 = CompaniesFactory.createCompanies1(); |
08 |
Set<CompanyDetails>
set2 = CompaniesFactory.createCompanies2(); |
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接口和Comaprator接口
Comparable故名思意是比较,意思就是做比较的,然后进行排序. 1.什么是comparable接口 此接口强行对实现它的每个类的对象进行整体排序.此排序被称为该类的自然排序 ,类的 compar ...
- java.lang.Comparable接口
转自:http://blog.csdn.net/zccst/article/details/5092920 java.lang.Comparable 接口 作者: zccst java.lang.Co ...
- java实现Comparable接口和Comparator接口,并重写compareTo方法和compare方法
原文地址https://segmentfault.com/a/1190000005738975 实体类:java.lang.Comparable(接口) + comareTo(重写方法),业务排序类 ...
- Java之comparable接口
comparable 接口: 1. 问题:java.util.Collections 类中的方法 Collections.sort(List list) 是根据什么确定容器中对象的“大小”顺序的? 2 ...
- Java之Comparable接口和Comparator接口
Comparable & Comparator 都是用来实现集合中元素的比较.排序的: Comparable 是在集合内部定义的方法实现的排序: Comparator 是在集合外部实现的排序: ...
- Java.lang.Comparable接口和Java.util.Comparator接口的区别
Java的Comparator和Comparable当需要排序的集合或数组不是单纯的数字型时,通常可以使用Comparator或Comparable,以简单的方式实现对象排序或自定义排序. 1.Com ...
- java lang(Comparable接口) 和java util(Comparator接口)分析比较
//Comparable 接口强行对实现它的每个类的对象进行整体排序. -- 自然排序.类的compareTo称为自然比较方法. public interface Comparable<T> ...
- Java中Comparable接口和Comparator接口的简单用法
对象比较器 1.Comparable接口 此接口强行对实现它的每个类的对象进行整体排序,这种排序成为类的自然排序,类的compareTo方法称为类的自然比较方法. 代码示例 import java.u ...
- java通过Comparable接口实现字符串比较大小排序的简单实例
/** * 对象比较大小compare的用法 字符串排序 * 练习代码, 给定字符串" nba" "cba" "ncaa" "wb ...
随机推荐
- 【转】使用CNPM搭建私有NPM
最近的Node项目中因为数据模型等问题,需要有一个对各个模块进行统一的管理,如果把私有的模型publish到公共的npm不太合适,所以决定使用cnpm搭建一个私有的npm,同时也可以对项目常用的npm ...
- Rescue(BFS时间最短 另开数组或优先队列)
Angel was caught by the MOLIGPY! He was put in prison by Moligpy. The prison is described as a N * M ...
- 20145214 《Java程序设计》第4周学习总结
20145214 <Java程序设计>第4周学习总结 教材学习内容总结 继承 继承基本上就是避免多个类间重复定义共同行为.要避免在程序设计上出现重复,可以把相同的程序代码提升为父类. 关键 ...
- ubuntu上的inpack测试
测试linpack 配置 配置linpack环境是整个过程中最麻烦的,也可能是因为我在配置的过程中出现了很多小问题吧.大概有3天的时间除了上课就在配置环境. 问题 总结起来问题和解决方法有这些 1.路 ...
- ubuntu下修改MySQL的配置文件my.cnf
先sudo su转换成root,再用cd转到/etc/MySQL目录下,用chmod修改权限(chmod 755 my.cnf),但这样还不能修改,再用vi命令(vi my.cnf),通过上下方向键将 ...
- Qt在VS(Visual Studio)中使用
版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Qt在VS(Visual Studio)中使用 本文地址:https://www.te ...
- docker中crontab无法运行
yum install -y crontabssed -ri 's/.*pam_loginuid.so/#&/' /etc/pam.d/crond vi /etc/pam.d/crond ...
- 细说匿名内部类引用方法局部变量时为什么需要声明为final
一.前言 在研究公司某个项目的源码,看到前人使用了挺多内部类,内部类平时我用的比较多的是匿名内部类,平时用的多的是匿名内部类,其他形式的用的比较少,然后我就有个疑惑:到底内部类是基于什么样的考虑,才让 ...
- [OS] 操作系统-进程线程-经典面试笔试题
题目转自:http://blog.csdn.net/morewindows/article/details/7392749 ·线程的基本概念.线程的基本状态及状态之间的关系? 线程,有时称为轻量级进程 ...
- 【Linux】linux中删除指定文件外所有其他文件(夹)的问题
今天碰到要删除指定文件(夹)外的其他文件的问题.网上查到的方法是这样的 需要在当前文件夹中进行: rm -rf !(keep) #删除keep文件之外的所有文件 rm -rf !(keep1|keep ...