【Java】連想配列、HashMapの利用

2018年3月11日

本稿は連想配列を扱うためのクラスであるHashMapの利用方法を説明します。

HashMapは「キー、値」のペアでデータを保持することができます。

例えば、キャラクターのステータスを名前ごとにデータを保持したい場合などに使えそうです。

配列よりも目的の値が高速に取り出せるというメリットもあります。

ゲームであろうが、それ以外のシステム開発であろうが、ほぼ使用するため、必須の知識といえるでしょう。

インスタンス化

HashMapのインスタンス化は、HashMap<キークラス,値クラス> map = new HashMap<>();で定義できます。

キークラスにはデータの呼び出しに使用したい別のデータを指定し、値クラスにはキークラスと関連付けたいデータを指定します。

文字列をキーに、数値を保持するためのHashMapを作成するサンプルです。

import java.util.HashMap;

public class Test {
	public static void main(String[] args) throws IOException {
		//文字列型をキーに、数値を値に指定
		HashMap<String, Integer> map = new HashMap<>();
	}
}

データを追加するputメソッド

putメソッドを利用するとキーと値の関連付けを行い追加します。

それではキャラクタのステータスをキャラクターの名前ごとに保持するHashMapを作成し、値を追加してみましょう。

import java.util.HashMap;

public class Test {
	public static void main(String[] args) {
		//文字列型をキーに、キャラを値に指定
		HashMap<String, Chara> map = new HashMap<>();
		Chara ch1 = new Chara("勇者",100, 30);
		Chara ch2 = new Chara("魔法使い", 30, 100);
		Chara ch3 = new Chara("すけこまし人", 50, 50);
		
		//名前でキャラを関連付け
		map.put(ch1.name, ch1);
		map.put(ch2.name, ch2);
		map.put(ch3.name, ch3);
		
		System.out.println(map);
	}
}
//キャラクタクラス
class Chara{
	String name;
	int hp;
	int mp;
	Chara(String name, int hp, int mp){
		this.name = name;
		this.hp = hp;
		this.mp = mp;
	}
	public String toString(){
		return "名前:"+name+" HP:"+hp+" MP:"+mp+"\n";
	}
}
実行結果

{勇者=名前:勇者 HP:100 MP:30
, 魔法使い=名前:魔法使い HP:30 MP:100
, すけこまし人=名前:すけこまし人 HP:50 MP:50
}

うまく名前にステータスが関連づきました。

勇者=名前:勇者 HP:100 MP:30の部分は「勇者」という文字列には名前が勇者で、HPが100でMPが30のデータが関連づいていることを表しています。

関連づいたデータを取得するgetメソッド

getメソッドの引数にキーを指定すれば、それに関連づいたデータを取得することができます。

import java.util.HashMap;

public class Test {
	public static void main(String[] args) {
		//文字列型をキーに、キャラを値に指定
		HashMap<String, Chara> map = new HashMap<>();
		Chara ch1 = new Chara("勇者",100, 30);
		Chara ch2 = new Chara("魔法使い", 30, 100);
		Chara ch3 = new Chara("すけこまし人", 50, 50);
		
		//名前でキャラを関連付け
		map.put(ch1.name, ch1);
		map.put(ch2.name, ch2);
		map.put(ch3.name, ch3);
		
		//「魔法使い」に関連づいたデータを取得します。
		Chara ch = map.get("魔法使い");
		ch.print();
	}
}
class Chara{
	String name;
	int hp;
	int mp;
	Chara(String name, int hp, int mp){
		this.name = name;
		this.hp = hp;
		this.mp = mp;
	}
	void print(){
		System.out.println(name);
		System.out.println(hp);
		System.out.println(mp);
	}
}
実行結果

魔法使い
30
100

データを削除するremoveメソッド

removeメソッドにキーを指定することで、関連づいたペアを削除できます。

import java.util.HashMap;

public class Test {
	public static void main(String[] args) {
		//文字列型をキーに、キャラを値に指定
		HashMap<String, Chara> map = new HashMap<>();
		Chara ch1 = new Chara("勇者",100, 30);
		Chara ch2 = new Chara("魔法使い", 30, 100);
		Chara ch3 = new Chara("すけこまし人", 50, 50);
		
		//名前でキャラを関連付け
		map.put(ch1.name, ch1);
		map.put(ch2.name, ch2);
		map.put(ch3.name, ch3);
		
		//「魔法使い」に関連づいたペアを削除します。
		map.remove("魔法使い");
		System.out.println(map);
	}
}
class Chara{
	String name;
	int hp;
	int mp;
	Chara(String name, int hp, int mp){
		this.name = name;
		this.hp = hp;
		this.mp = mp;
	}
	public String toString(){
		return "名前:"+name+" HP:"+hp+" MP:"+mp+"\n";
	}
}
実行結果

{勇者=名前:勇者 HP:100 MP:30
, すけこまし人=名前:すけこまし人 HP:50 MP:50
}

魔法使いが削除されていますので結果に魔法使いが表示されなくなりました。

追加したデータをすべて取り出す方法

データを取り出す方法はいくつかあるのですが、まずは使いやすいキー一覧を取得する方法から紹介します。

import java.util.HashMap;

public class Test {
	public static void main(String[] args) {
		//文字列型をキーに、キャラを値に指定
		HashMap<String, Chara> map = new HashMap<>();
		Chara ch1 = new Chara("勇者",100, 30);
		Chara ch2 = new Chara("魔法使い", 30, 100);
		Chara ch3 = new Chara("すけこまし人", 50, 50);
		
		//名前でキャラを関連付け
		map.put(ch1.name, ch1);
		map.put(ch2.name, ch2);
		map.put(ch3.name, ch3);
		
		//関連づいたデータを全取得します。
		for ( String name : map.keySet() ) {
			Chara ch = map.get(name);
			ch.print();
			System.out.println();
		}
	}
}
class Chara{
	String name;
	int hp;
	int mp;
	Chara(String name, int hp, int mp){
		this.name = name;
		this.hp = hp;
		this.mp = mp;
	}
	void print(){
		System.out.println(name);
		System.out.println(hp);
		System.out.println(mp);
	}
}
実行結果

勇者
100
30

魔法使い
30
100

すけこまし人
50
50

ループの部分を下記のように変えても同じ結果が得られます。

		//関連づいたデータを全取得します。
		for ( Chara ch : map.values() ) {
			ch.print();
			System.out.println();
		}

また、キーと値の両方を取得する方法もあります。

import java.util.HashMap;
import java.util.Map;

public class Test {
	public static void main(String[] args) {
		//文字列型をキーに、キャラを値に指定
		HashMap<String, Chara> map = new HashMap<>();
		Chara ch1 = new Chara("勇者",100, 30);
		Chara ch2 = new Chara("魔法使い", 30, 100);
		Chara ch3 = new Chara("すけこまし人", 50, 50);
		
		//名前でキャラを関連付け
		map.put(ch1.name, ch1);
		map.put(ch2.name, ch2);
		map.put(ch3.name, ch3);
		
		//関連づいたデータを全取得します。
		for ( Map.Entry<String, Chara> e : map.entrySet() ) {
			String key = e.getKey();
			System.out.println("キー="+key);
			Chara ch = e.getValue();
			ch.print();
			System.out.println();
		}
	}
}
class Chara{
	String name;
	int hp;
	int mp;
	Chara(String name, int hp, int mp){
		this.name = name;
		this.hp = hp;
		this.mp = mp;
	}
	void print(){
		System.out.println(name);
		System.out.println(hp);
		System.out.println(mp);
	}
}
実行結果

キー=勇者
勇者
100
30

キー=魔法使い
魔法使い
30
100

キー=すけこまし人
すけこまし人
50
50

キーに対して複数の値を関連付け

HashMapはキー、値の関連付けを実現しますが、キーに対して複数のデータを関連付けたい場合もあります。

総称型の中には当然配列や別のArrayListなどの総称型クラスを含めることができます。

規模の小さいゲームで使うことは、まずないですが規模が大きくなると使うかもしれません。

総称型を入れ子にしすぎるとスパゲッティ化するのでほどほどに・・・

それではサンプルですが、ゲームでいくつか魔法を作ったとして、魔法を属性ごとのグループに分けて表示してみます。

import java.util.HashMap;
import java.util.ArrayList;

public class Test {
	public static void main(String[] args) {
		
		
		Magic[] magicList = new Magic[]{
			new Magic("ファイアー","火")
			,new Magic("フリーズ","氷")
			,new Magic("サンダー","雷")
			,new Magic("フラッシュ","光")
			,new Magic("フレイム","火")
			,new Magic("ライトニング","雷")
			,new Magic("ホーリーアロー","光")
			,new Magic("ヒール","光")
		};
		
		//属性ごとに魔法を分ける
		HashMap<String, ArrayList<Magic>> elementMap = new HashMap<>();
		
		for ( Magic magic : magicList ) {
			ArrayList<Magic> arr = elementMap.get(magic.element);
			
			//関連付けがない場合はnullが取得されるためnull判定
			if ( arr == null ) {
				arr = new ArrayList<>();
				elementMap.put(magic.element,arr);
			}
			arr.add(magic);
		}
		
		//結果を表示
		for ( String elem : elementMap.keySet() ) {
			ArrayList<Magic> arr = elementMap.get(elem);
			System.out.println("====="+elem+"=====");
			for ( Magic magic : arr ) {
				System.out.println(magic.name);
			}
			System.out.println("====="+elem+"=====");
		}
	}
}
class Magic{
	String name;
	String element;
	Magic(String name, String element){
		this.name = name;
		this.element = element;
	}
}
実行結果

=====氷=====
フリーズ
=====氷=====
=====雷=====
サンダー
ライトニング
=====雷=====
=====光=====
フラッシュ
ホーリーアロー
ヒール
=====光=====
=====火=====
ファイアー
フレイム
=====火=====

ラムダ式とStreamAPIを使える人はより簡単にグルーピングできます。

import java.util.Map;
import java.util.List;
import java.util.Arrays;
import java.util.stream.Collectors;

public class Test {
	public static void main(String[] args) {
		
		
		Magic[] magicList = new Magic[]{
			new Magic("ファイアー","火")
			,new Magic("フリーズ","氷")
			,new Magic("サンダー","雷")
			,new Magic("フラッシュ","光")
			,new Magic("フレイム","火")
			,new Magic("ライトニング","雷")
			,new Magic("ホーリーアロー","光")
			,new Magic("ヒール","光")
		};
		
		//属性ごとに魔法を分ける
		Map<String, List<Magic>> elementMap = 
			Arrays.stream(magicList)
			.collect(Collectors.<Magic,String>groupingBy(e->e.element));
		
		//結果の表示
		elementMap.forEach((elem,v)->{
			System.out.println("====="+elem+"=====");
			v.stream().forEach(e->System.out.println(e.name));
			System.out.println("====="+elem+"=====");
		});
	}
}
class Magic{
	String name;
	String element;
	Magic(String name, String element){
		this.name = name;
		this.element = element;
	}
}

=====雷=====
サンダー
ライトニング
=====雷=====
=====氷=====
フリーズ
=====氷=====
=====光=====
フラッシュ
ホーリーアロー
ヒール
=====光=====
=====火=====
ファイアー
フレイム
=====火=====

Java

Posted by nompor