水曜日, 4月 10, 2013

Commons Lang - EqualsBuilder & HashCodeBuilder

equals() と hashCode()

Javaでドメインモデル設計の際重要になってくるequals()とhashCode()のオーバーライド.
これを自分で実装するのは結構めんどうくさいし,コードの可読性も決して高くはないのが通常だろうと思う.そんな時にお勧めなのはやはりApache CommonsのEqualsBuilderとHashCodeBuilderだろう.

サンプル

以下のようなトレードクラスがあったとする.
このクラスの主キーフィールドはtradeIdとeventIdとしよう.
equals()とhashCode()ではそれぞれEqualsBuilderとHashCodeBuilderを使って複合主キーを比較している.この例では非主キーのフィールドは比較をしていない.

import java.util.Date;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class Trade {
 
 private String tradeId;
 private String eventId;
 private Date settleDate;
 private Date tradeDate;
 private int counterPartyId;
 
 public Trade( String tradeId, String eventId, Date settleDate, Date tradeDate, int counterPartyId ) {
  this.tradeId = tradeId;
  this.eventId = eventId;
  this.settleDate = settleDate;
  this.tradeDate = tradeDate;
  this.counterPartyId = counterPartyId;
 }
 
 public boolean equals( Object obj ) {
  boolean result = false;
  if( obj instanceof Trade ) {
   Trade other = (Trade)obj;
   result = new EqualsBuilder().append( tradeId, other.getTradeId() )
     .append( eventId, other.eventId )
     .isEquals();
  }
  return result;
 }
 
 public int hashCode() {
  return new HashCodeBuilder().append( tradeId )
    .append( eventId ).toHashCode();
 }
 
 public String getTradeId() { return tradeId; }
 public String getEventId() { return eventId; }
 public Date getSettleDate() { return settleDate; }
 public Date getTradeDate() { return tradeDate; }
 public int getCounterPartyId() { return counterPartyId; }
}



それでもってこのTradeのequals()とhashCode()のテストクラスが以下の通り.
以下のテストで分かる通り,trd6は主キーが同じなのに非主キーフィールドは異なっているという異常ケースである.上記の実装ではこのような例外ケースには対応できていない.理想的には対応したほうがよい,と思うがとりあえずそれをするかどうかはプロジェクトの様々なvariablesを考慮した結果次第,だろうか.



import java.text.ParseException;
import java.text.SimpleDateFormat;

import org.junit.Test;
import static org.junit.Assert.*;

public class BuilderSampleTest {

 @Test
 public void testEqualsAndHashCode() throws ParseException {
  SimpleDateFormat sdf = new SimpleDateFormat( "yyyy/MM/dd" );
  Trade trd1 = new Trade( "TRD123", "21-123221", sdf.parse( "2012/03/04" ), sdf.parse( "2012/04/04" ), 565000 );
  Trade trd2 = new Trade( "TRD238", "21-123250", sdf.parse( "2012/03/05" ), sdf.parse( "2012/04/04" ), 565000 );
  Trade trd3 = new Trade( "TRD123", "21-123250", sdf.parse( "2012/03/05" ), sdf.parse( "2012/04/09" ), 565300 );
  Trade trd4 = new Trade( "TRD126", "21-123221", sdf.parse( "2012/03/04" ), sdf.parse( "2012/04/04" ), 565000 );
  Trade trd5 = new Trade( "TRD123", "21-123221", sdf.parse( "2012/03/04" ), sdf.parse( "2012/04/04" ), 565000 );
  Trade trd6 = new Trade( "TRD123", "21-123221", sdf.parse( "2012/05/04" ), sdf.parse( "2012/06/04" ), 620000 );
  
  // trd1 and trd2 - composite primary key different
  assertTrue( !trd1.equals( trd2 ) );
  // trd1 and trd3 - composite primary key different
  assertTrue( !trd1.equals( trd3 ) );
  // trd1 and trd4 - composite primary key different
  assertTrue( !trd1.equals( trd4 ) );
  // trd1 and trd5 - both composite primary key and non-key fields are same. Of course.
  assertTrue( trd1.equals( trd5 ) );
  // trd1 and trd6 - composite primary key is same but non-key fields are different - WHAT?!
  assertTrue( trd1.equals( trd6 ) );
  // trd1 and non-Trade object
  assertTrue( !trd1.equals( new String() ) );
 }
}

0 件のコメント: