自作クラスのオブジェクトをハッシュテーブルに収めるJavaTips 〜Javaプログラミング編

» 2005年04月13日 10時00分 公開
[小田原大@IT]

 ハッシュテーブルは要素の集合を表すデータ構造で、キーによって内部の要素を指定することができます。配列は非負の整数値をインデックスとして、要素にアクセスすることができるのに対し、ハッシュテーブルでは整数に限らず、さまざまなものをキーとして用いることができます。Javaでは、java.util.HashtableまたはJDK 1.2より提供されたjava.util.HashMapによってこのデータ構造を用いることができます。

 情報サイト名とそのURLを対応付ける例を示します。

Hashtableの使用例
// import java.net.URL;, import java.util.Hashtable; が必要
Hashtable h = new Hashtable();
h.put(“CNET Japan”, new URL(“http://japan.cnet.com/”));
h.put(“PCWEB”, new URL(“http://pcweb.mycom.co.jp/”));
h.put(“@IT”, new URL(“http://www.atmarkit.co.jp/”));
System.out.println(h.get(“@IT”));


 これはString型のオブジェクトで表したサイト名をキーとして、java.net.URL型のオブジェクトを値として対応させたものです。この対応付けはputメソッドで行います。第1引数にキーを、第2引数に値を指定します。なお、J2SE 5.0より前のバージョンではここに直接基本型を入れることはできません。キーを指定して値を取り出すにはgetメソッドを用います。

自作クラスを用いる

 Hashtableに自作のクラスのオブジェクトをキーとして用いる、もしくは値として格納することができます。ただし、そのクラスに、equalsとhashCodeというObjectクラスの持つ2つのメソッドをオーバーライドする必要があります。標準APIのクラスにはこれらのメソッドがきちんと定義されています。2つのメソッドについて説明します。

●equals

 自身と引数に取ったオブジェクトが等価であるかどうかをboolean値で返すように定義します。

 自作クラスの等価性の基準は自由に定めることができますが、同値関係を満たす必要があります。オブジェクトa、 bについて、以下の3つの条件が成り立てば同値関係を満たしています。

  1. a.equals(b)==true なら b.equals(a)==true である(対称律)
  2. a.equals(a)==true である(反射律)
  3. a.equals(b)==true かつ b.equals(c)==true なら a.equals(c) である(推移律)

 例えば「血液型」についてクラスを定義したとします。

BloodType.java
class BloodType {
  String genotype;
  String phenotype;
}


 血液型には遺伝子型(AA、BOなど)と表現型(A、Bなど)があります。上のコードではそれぞれString型のgenotype、phenotypeで表しています。例えば輸血の際に判定が必要なのは表現型ですが、この判定を目的として、BloodTypeのequalsを定義するなら、以下のとおりになります。

BloodTypeのequals(表現型の等価性のみ着目)
public boolean equals(Object obj){
  if(!(obj instanceof BloodType)){ return false; } // 型の判定
    BloodType bt = (BloodType)obj;
    return phenotype.equals(bt.phenotype);
}


 判定部分(赤字)ではphenotypeのみ着目しgenotypeは無視しています。このようなオーバーライド定義ではgenotypeが異なる2つのオブジェクトが等しいと判定されることがありますが、目的が表現型の等価性の判定なら問題ないわけです。「genotypeもphenotypeも等しい」というより厳しい基準によってequalsを定義しても構いません。

●hashCode

 hashCodeはint型のハッシュ値を返すメソッドです。ハッシュ値はハッシュテーブルにデータが収められるアドレスを指定する値です。アドレスを指定するとはいっても、以下の条件を満たせばどのような整数値を与えても構いません。

  1. equalsで等しいと判定されたオブジェクトは等しいハッシュ値を持つ
  2. equalsで等価性の判定に用いられたフィールドに変更がない限り常に同じハッシュ値を持つ

 ハッシュ値の条件には、「equalsメソッドで等しくないと判定された場合には異なるハッシュ値を持たなければならない」という条件はないので、2つの等価でないオブジェクトのハッシュ値が等しくても構いません。極端な話hashCode()の中身が「return 0;」だけであっても(つまり、どのオブジェクトもハッシュ値が0となります)、ハッシュテーブルに用いることはできます。ハッシュ値が等しい場合、アドレスの衝突が起こるのですが、その場合には格納されるアドレスを変更するなどの解決が内部で自動的に行われます。

 ただしハッシュテーブルのアクセス速度などのパフォーマンスを向上させるために、等価でないオブジェクトはできる限り異なるハッシュ値を返すようにするのがよいでしょう。上のBloodTypeクラスでhashCodeを定義するなら、例えば、

BloodTypeのhashCode()の実装
public int hashCode(){
  return phenotype.hashCode();
}


など、フィールドのhashCode()を用いて定義することができます。もちろんphenotypeが等しいときにはhashCode()は同じ値を返すので、equalsがtrueのオブジェクトは同じハッシュ値を返します。

 このようにequalsに関するフィールドをhashCode()の定義に用いることで、フィールドが変化した際にも1の条件である、equalsがtrueのときにはhashCodeが同じ値を返すという一貫性が保たれることになります。

 これら2つのメソッドを定義することによって、クラスのオブジェクトをハッシュテーブルで用いることができるようになります。

Profile

WINGSプロジェクト

小田原大


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。