JPCERT コーディネーションセンター

MET09-J. equals() メソッドを実装するクラスでは hashCode() メソッドも実装する

Object.equals()メソッドをオーバーライドするクラスは Object.hashCode()メソッドもオーバーライドしなくてはならない。java.lang.Objectクラスでは、equalsメソッドを使って等価であると評価される2つのオブジェクトはすべて、hashCode()メソッドの呼び出し結果が同じ整数値になることを要求している。[API 2006]

equals()メソッドはオブジェクト同士の論理的等価性を評価するために用いられる。したがって、等価なオブジェクトにhashCode()メソッドを適用した結果は同じ値でなければならない。この契約に従わない実装を行うと、プログラムの欠陥になる。

違反コード

以下の違反コードでは、HashMapを使ってクレジットカード番号と文字列を関連づけ、続いてクレジットカード番号に関連づけられた文字列を取得しようとしている。取得される値は4111111111111111であるはずが、実際にはnullが取得される。

public final class CreditCard {
  private final int number;

  public CreditCard(int number) {
    this.number = number;
  }

  public boolean equals(Object o) {
    if (o == this) {
      return true;
    } 
    if (!(o instanceof CreditCard)) {
      return false;
    }
    CreditCard cc = (CreditCard)o;
    return cc.number == number; 
  }

  public static void main(String[] args) {
    Map<CreditCard, String> m = new HashMap<CreditCard, String>();
    m.put(new CreditCard(100), "4111111111111111");
    System.out.println(m.get(new CreditCard(100)));  
  }
}

このように間違った動作をする原因は、CreditCardクラスではequalsメソッドをオーバーライドしているが、hashCodeメソッドをオーバーライドしていないからである。したがって、デフォルトのhashCodeメソッドが使われ、オブジェクトが論理的には同一であっても異なる値を返してしまう。このため、異なるハッシュバケツを参照してしまい、get()メソッドは意図した値を見つけることができない。さらにこのコードは、main()でクレジットカード番号を指定しており、「MSC03-J. センシティブな情報をハードコードしない」にも違反している。

適合コード

以下の解決法では hashCode()もオーバーライドしており、equals()メソッドが同一であると見なすインスタンスに対して同一の値を生成することを保証している。Bloch はこのようなハッシュ関数の実装方法について詳細に検討している。[Bloch 2008]

public final class CreditCard {
  private final int number;
  
  public CreditCard(int number) {
    this.number = number;
  }

  public boolean equals(Object o) {
    if (o == this) {
      return true;
    } 
    if (!(o instanceof CreditCard)) {
      return false;
    }
    CreditCard cc = (CreditCard)o;
    return cc.number == number; 
  }

  public int hashCode() {
    int result = 17;
    result = 31 * result + number;
    return result;
  }

  public static void main(String[] args) {
    Map<CreditCard, String> m = new HashMap<CreditCard, String>();
    m.put(new CreditCard(100), "4111111111111111");
    System.out.println(m.get(new CreditCard(100)));
  }
}
リスク評価

hashCodeメソッドをオーバーライドせずにequalsメソッドをオーバーライドすると、予期せぬ結果をもたらす。

ルール 深刻度 可能性 修正コスト 優先度 レベル
MET09-J P1 L3
自動検出

equals()メソッドとhashCode()メソッドのいずれか一方のみをオーバーライドしているクラスの検出は容易であるが、これらのメソッドが相互に一貫性を持つかどうかを判別するのは一般に困難である。ヒューリスティックな手法が役に立つ可能性はある。

関連ガイドライン
MITRE CWE CWE-581. Object model violation: Just one of equals and hashcode Defined
参考文献
[API 2006] Class Object
[Bloch 2008] Item 9. Always override hashCode when you override equals
翻訳元

これは以下のページを翻訳したものです。

MET09-J. Classes that define an equals() method must also define a hashCode() method (revision 70)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter