ハッシュテーブルは要素の集合を表すデータ構造で、キーによって内部の要素を指定することができます。配列は非負の整数値をインデックスとして、要素にアクセスすることができるのに対し、ハッシュテーブルでは整数に限らず、さまざまなものをキーとして用いることができます。Javaでは、java.util.HashtableまたはJDK 1.2より提供されたjava.util.HashMapによってこのデータ構造を用いることができます。
情報サイト名とそのURLを対応付ける例を示します。
// import java.net.URL;, import java.util.Hashtable; が必要 |
これはString型のオブジェクトで表したサイト名をキーとして、java.net.URL型のオブジェクトを値として対応させたものです。この対応付けはputメソッドで行います。第1引数にキーを、第2引数に値を指定します。なお、J2SE 5.0より前のバージョンではここに直接基本型を入れることはできません。キーを指定して値を取り出すにはgetメソッドを用います。
Hashtableに自作のクラスのオブジェクトをキーとして用いる、もしくは値として格納することができます。ただし、そのクラスに、equalsとhashCodeというObjectクラスの持つ2つのメソッドをオーバーライドする必要があります。標準APIのクラスにはこれらのメソッドがきちんと定義されています。2つのメソッドについて説明します。
自身と引数に取ったオブジェクトが等価であるかどうかをboolean値で返すように定義します。
自作クラスの等価性の基準は自由に定めることができますが、同値関係を満たす必要があります。オブジェクトa、 bについて、以下の3つの条件が成り立てば同値関係を満たしています。
例えば「血液型」についてクラスを定義したとします。
class BloodType { |
血液型には遺伝子型(AA、BOなど)と表現型(A、Bなど)があります。上のコードではそれぞれString型のgenotype、phenotypeで表しています。例えば輸血の際に判定が必要なのは表現型ですが、この判定を目的として、BloodTypeのequalsを定義するなら、以下のとおりになります。
public boolean equals(Object obj){ |
判定部分(赤字)ではphenotypeのみ着目しgenotypeは無視しています。このようなオーバーライド定義ではgenotypeが異なる2つのオブジェクトが等しいと判定されることがありますが、目的が表現型の等価性の判定なら問題ないわけです。「genotypeもphenotypeも等しい」というより厳しい基準によってequalsを定義しても構いません。
hashCodeはint型のハッシュ値を返すメソッドです。ハッシュ値はハッシュテーブルにデータが収められるアドレスを指定する値です。アドレスを指定するとはいっても、以下の条件を満たせばどのような整数値を与えても構いません。
ハッシュ値の条件には、「equalsメソッドで等しくないと判定された場合には異なるハッシュ値を持たなければならない」という条件はないので、2つの等価でないオブジェクトのハッシュ値が等しくても構いません。極端な話hashCode()の中身が「return 0;」だけであっても(つまり、どのオブジェクトもハッシュ値が0となります)、ハッシュテーブルに用いることはできます。ハッシュ値が等しい場合、アドレスの衝突が起こるのですが、その場合には格納されるアドレスを変更するなどの解決が内部で自動的に行われます。
ただしハッシュテーブルのアクセス速度などのパフォーマンスを向上させるために、等価でないオブジェクトはできる限り異なるハッシュ値を返すようにするのがよいでしょう。上のBloodTypeクラスでhashCodeを定義するなら、例えば、
public int hashCode(){ |
など、フィールドのhashCode()を用いて定義することができます。もちろんphenotypeが等しいときにはhashCode()は同じ値を返すので、equalsがtrueのオブジェクトは同じハッシュ値を返します。
このようにequalsに関するフィールドをhashCode()の定義に用いることで、フィールドが変化した際にも1の条件である、equalsがtrueのときにはhashCodeが同じ値を返すという一貫性が保たれることになります。
これら2つのメソッドを定義することによって、クラスのオブジェクトをハッシュテーブルで用いることができるようになります。
Copyright © ITmedia, Inc. All Rights Reserved.