Help us understand the problem. What is going on with this article?

JavaのMapで複合キーを使用する。

More than 1 year has passed since last update.

概要

JavaのMapで複合キーを使うには、Mapのキーに、複合キーで使いたいフィールドを全て保持するクラスを指定する。
このとき、キーに使うクラスの以下のメソッドをオーバーライドする。

  • equals()
  • hashCode()

手順

  1. 複合キーに使いたいフィールドを保持するクラスを作成する。
  2. このクラスが暗黙的に継承するObjectクラスのメソッドequalsとhashCodeをオーバーライドする。
  3. Mapクラスのキーに作成したクラスを指定する。

以下のような売上金を保持するクラスを考え、このクラスを複合キーを使ったMapで管理してみる。

売上金クラスのフィールド
・店舗番号
・売上日
・売上金額

複合キーに使うフィールド
・店舗番号
・売上日

① 複合キーに使いたいフィールドを保持するクラスを作成する。

複合キークラス
package hoge;

import java.time.LocalDate;

/** 複合キー */
public class CompositeKey {

    /** 店舗番号 */
    private final Integer shopNo;

    /** 売上日 */
    private final LocalDate salesDate;

    public CompositeKey(Integer storeNo, LocalDate salesDate) {
        this.shopNo = storeNo;
        this.salesDate = salesDate;
    }
}

② そのクラスのequalsメソッドとhashCodeメソッドをオーバーライドする。

<Eclipseを使う場合の手順>

  1. パッケージ・エクスプローラを開き、複合キーをフィールドに持つクラスを選択する。
  2. 画面上部のメニュー > ソース(S) > 「hashCode()およびequals()の生成(H)...」をクリック
  3. ダイアログ「hashCode()およびequals()の生成」が表示されるので、O.K.を押す。
  4. equalsメソッドとhashCodeメソッドが複合キーのクラスに挿入される。

equalsメソッドとhashCodeメソッドは、すべてのクラスの継承元となるObjectクラスに定義されているため、この操作は、Objectクラスのメソッドをオーバーライドする事になる。

○ オーバーライドする理由

・equalsメソッド
オーバーライドすることにより、全てのフィールドが一致する場合のみ、同じオブジェクトとして判定されるようになる。 オーバーライドしないと、フィールドの中身で比較されないので、複合キーとして使えない。

・hashCodeメソッド
オーバーライドすることによって、各フィールドの値を元にHash値が生成されるようになる。

equals()およびhashCode()を追加した複合キークラス

複合キークラス
package hoge;

import java.time.LocalDate;

/** 複合キー */
package hoge;

import java.time.LocalDate;

/** 複合キー */
public class CompositeKey {

    /** 店舗番号 */
    private final Integer shopNo;

    /** 売上日 */
    private final LocalDate salesDate;

    public CompositeKey(Integer storeNo, LocalDate salesDate) {
        this.shopNo = storeNo;
        this.salesDate = salesDate;
    }

    /* (非 Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((salesDate == null) ? 0 : salesDate.hashCode());
        result = prime * result + ((shopNo == null) ? 0 : shopNo.hashCode());
        return result;
    }

    /* (非 Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        CompositeKey other = (CompositeKey) obj;
        if (salesDate == null) {
            if (other.salesDate != null)
                return false;
        } else if (!salesDate.equals(other.salesDate))
            return false;
        if (shopNo == null) {
            if (other.shopNo != null)
                return false;
        } else if (!shopNo.equals(other.shopNo))
            return false;
        return true;
    }
}

③ 動作確認

<テスト・シナリオ>

HashMapに売上金データを格納し、複合キーで値を取得する。

○ 準備

1. テストデータを用意する。
  • 売上金
店舗番号 売上日 売上金額
1 2017/01/01 100
2 2017/01/02 200
3 2017/01/03 300
2. 売上金を保持するクラスを作成。
売上金クラス
package hoge;

import java.time.LocalDate;

/** 売上レコード */
public class SalesRecord {

    /** 店舗番号 */
    private final Integer shopNo;

    /** 売上日 */
    private final LocalDate salesDate;

    /** 売上金額 */
    private final Integer sales;

    public SalesRecord(Integer storeNo, LocalDate salesDate, Integer sales) {
        this.shopNo = storeNo;
        this.salesDate = salesDate;
        this.sales = sales;
    }

    public Integer getStoreNo() {
        return shopNo;
    }

    public LocalDate getSalesDate() {
        return salesDate;
    }

    public Integer getSales() {
        return sales;
    }

    /** テスト用 */
    @Override
    public String toString() {
        return "SalesRecord [storeNo=" + shopNo + ", salesDate=" + salesDate + ", sales=" + sales + "]";
    }
}
3. テストを実行するクラスを作成。
Mainクラス
package hoge;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TestMain {

    public static void main(String[] args) {

        // テストデータ
        List<SalesRecord> salesRecords = new ArrayList<>();
        salesRecords.add(new SalesRecord(1,LocalDate.of(2017,1,1),100));
        salesRecords.add(new SalesRecord(2,LocalDate.of(2017,1,2),200));
        salesRecords.add(new SalesRecord(3,LocalDate.of(2017,1,3),300));

        // HashMapを生成
        Map<CompositeKey,SalesRecord> map = new HashMap<>();

        for (SalesRecord record : salesRecords) {
            map.put(new CompositeKey(record.getStoreNo(), record.getSalesDate()), record);
        }

        // KeyからValueを取り出せることを確認する。
        System.out.println(map.get(new CompositeKey(2,LocalDate.of(2017,1,2))));
    }
}

○ テスト実施

HashMapに格納した売上金データから、以下の複合キーでレコードを取得してみる。

店舗番号 売上日
2 2017/01/02

○ テスト結果

コンソール
SalesRecord [storeNo=2, salesDate=2017-01-02, sales=200]

指定したレコードを取り出せている事が確認できた。

サンプルコード

・GitHub
JavaのMapで複合キーを使用する

テストのために作成したコードは上記に格納した。
ただし、掲載したコードはMainクラスを含んでいるため、コピー&ペーストでも実行できる。

確認環境

  • Windows10 Home
  • Eclipse Neon.2 Release(4.6.2)
  • JavaSE-1.8
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away