Java的Comparable接口的一个陷阱

上一篇 / 下一篇  2012-05-10 09:42:11 / 个人分类:Java

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

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

Gu c})oK051Testing软件测试网7R!pP[7^ I

51Testing软件测试网| u8F`B6B+F(?

  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.     }  
  49. }

P`"udiuC0  这个实现看起来没问题,假设现在这个类提供的信息不够使用,我们又创建了这个类的一个子类CompanyDetail类用以扩展他。例如我们想以一个表的形式显示公司的信息,我们就可以用这个类。51Testing软件测试网4C:{;RFVx(|+^

51Testing软件测试网$C&[Q Z0[+f5D0E

4G:]hE+qoa0
  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.     }  
  43. }
这个类的实现看起来还是没什么问题,但是事实上是有问题的,我们可以写一个test指出问题在哪里。当我们没有对父类的所有细节加以注意时,问题就来了。51Testing软件测试网z?2w8Y0ux

51Testing软件测试网/@8J8Q9PL#}F7O

  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());
51Testing软件测试网Tp5qs&G%U Gt?

  我们构造了两个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()的需求。51Testing软件测试网TtG!WN1pA\

51Testing软件测试网0{)R&Jf&f0G#wKR

  这篇文章提到的问题非常普遍,但是经常被忽视。Comparable接口的问题实际是由于不好的约定和对使用的接口需求的错误理解造成的。作为 一个Java开发人员或架构师,你应该特别注意这样的事情,并遵守良好的编码习惯和做法。 越大的项目,这种问题就越显得重要。这里我总结了一个使用Comparable接口的最佳实践,可以避免这个错误。

-y:y5TM6Ael0F?}051Testing软件测试网I!]PVb

  Java的Comparable接口的设计和使用的最佳实践:

RHQ.h k)s1C$R?051Testing软件测试网/Th.G u2q&_+i D*K)]

  ● 了解你需要创建的领域对象,如果对象没有明确的排序规则,请不要实现Comparable接口。

eM u [i(~+L+W\d0

9f4E9Gs3`|3X3Jh0  ● 更多的使用Comparator而不是Comparable,Comparator在更多的业务使用方式时要显得更为实用。

n0p#r-[~051Testing软件测试网`?r#I,Xxj1W

  ● 如果你需要创建依赖Comparable接口的接口或者库,如果可能的话你提供自己的Comparator实现,否则就写一个良好的文档指明在你的接口实现类中如何实现。51Testing软件测试网I$M8K6I7I4\/c

u%ISM,V5~T7h0  ● 遵守良好的编码习惯和做法。Effective Java是很好的推荐。51Testing软件测试网&X4z3tA'y:u7{V:Ilv


TAG:

 

评分:0

我来说两句

Open Toolbar