Java的Comparable接口的一个陷阱

开发 后端
Java的Comparable接口提供一个对实现了这个接口的对象列表进行排序的办法。原始的排序对于简单的对象来说具有意义,但是当我们面对复杂的面向对象的业务逻辑对象时,事情变得复杂的多。

Java的Comparable接口提供一个对实现了这个接口的对象列表进行排序的办法。原始的排序对于简单的对象来说具有意义,但是当我们面对复杂的面向对象的业务逻辑对象时,事情变得复杂的多。从业务经理的角度来看,一些交易对象的自然顺序可能是按照交易的价值来排序的,但是从系统管理员的角度来看,这个排序的规则可能是交易的速度。所以在大多数情况下,并没有明确的业务领域对象的自然排序规则。

假设我们找到了一个需要排序的类,比如说Campany。我们把公司的offical name作为主关键字,把id作为次要关键字。这个类的实现如下:

  1. public class Company implements Comparable<Company> {  
  2.    
  3.     private final String id;  
  4.     private final String officialName;  
  5.    
  6.     public Company(final String id, final String officialName) {  
  7.         this.id = id;  
  8.         this.officialName = officialName;  
  9.     }  
  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.     }  

这个实现看起来没问题,假设现在这个类提供的信息不够使用,我们又创建了这个类的一个子类CompanyDetail类用以扩展他。例如我们想以一个表的形式显示公司的信息,我们就可以用这个类。

  1. public class CompanyDetails extends Company {  
  2.    
  3.     private final String marketingName;  
  4.     private final Double marketValue;  
  5.    
  6.     public CompanyDetails(final String id, final String officialName, final String marketingName, final Double marketValue) {  
  7.         super(id, officialName);  
  8.         this.marketingName = marketingName;  
  9.         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.     }  

这个类的实现看起来还是没什么问题,但是事实上是有问题的,我们可以写一个test指出问题在哪里。当我们没有对父类的所有细节加以注意时,问题就来了。

  1. CompanyDetails c1 = new CompanyDetails("231412""McDonalds Ltd""McDonalds food factory"120000.00);  
  2. CompanyDetails c2 = new CompanyDetails("231412""McDonalds Ltd""McDonalds restaurants"60000.00);  
  3.    
  4. Set<CompanyDetails> set1 = CompaniesFactory.createCompanies1();  
  5. set1.add(c1);  
  6. set1.add(c2);  
  7.    
  8. Set<CompanyDetails> set2 = CompaniesFactory.createCompanies2();  
  9. 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是很好的推荐。

原文链接:http://my.oschina.net/jack230230/blog/56339

【编辑推荐】

  1. Apache CXF实战之三:传输Java对象
  2. Java程序设计:图形与多媒体处理
  3. Java集合框架总结:TreeSet类的排序问题
  4. Java图形界面开发:高级Swing容器(三)
  5. Java理论与实践: Web层的状态复制
责任编辑:林师授 来源: OSCHINA
相关推荐

2009-09-02 14:59:35

Comparable接

2014-09-18 10:50:26

创业

2020-09-22 07:50:23

API接口业务

2024-02-28 08:12:25

SSE接口代理

2021-04-14 07:33:02

Java函数式断言

2021-05-21 07:26:15

DataSource接口数据库

2024-02-21 16:13:36

CNCF开源监控工具Prometheus

2019-11-20 23:44:29

接口数据加密数据安全

2022-05-16 10:45:22

Redis接口限流缓存

2022-06-21 14:44:38

接口数据脱敏

2018-09-13 14:18:20

C语言Java程序员

2022-01-06 14:59:53

Java框架magic-api

2020-04-20 17:15:32

Java开发代码

2016-09-26 17:26:20

2022-12-12 08:14:47

2014-10-14 15:50:19

UIAndroid

2014-12-08 10:39:15

2019-09-11 10:09:00

Java虚拟机算法

2009-06-01 14:54:50

jpaapiJava

2023-01-03 12:30:25

架构CPUGPU
点赞
收藏

51CTO技术栈公众号