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

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

+ equals()
+ hashCode()

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

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

<dl>
  <dt>売上金クラスのフィールド
</dt>
  <dd>
・店舗番号<br/>
・売上日<br/>
・売上金額<br/>
</dd>
<br/>
  <dt>複合キーに使うフィールド</dt>
  <dd>
・店舗番号<br/>
・売上日<br/>
</dd>
</dl>

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

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

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

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

<dl>
  <dt>・equalsメソッド</dt>
  <dd>オーバーライドすることにより、全てのフィールドが一致する場合のみ、同じオブジェクトとして判定されるようになる。
オーバーライドしないと、フィールドの中身で比較されないので、複合キーとして使えない。
</dd>
<br/>
  <dt>・hashCodeメソッド</dt>
  <dd>オーバーライドすることによって、各フィールドの値を元にHash値が生成されるようになる。</dd>
</dl>

#### equals()およびhashCode()を追加した複合キークラス
```java:複合キークラス
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. 売上金を保持するクラスを作成。

```java:売上金クラス
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. テストを実行するクラスを作成。

```java: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 |


#### ○ テスト結果

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

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

### サンプルコード
・GitHub
[JavaのMapで複合キーを使用する](https://github.com/nogitsune413/hashMapCompositeKey)

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

### 確認環境
+ Windows10 Home
+ Eclipse Neon.2 Release(4.6.2)
+ JavaSE-1.8