JavaでMapのループを実装する際に、クラス選択に迷うケースは少なくありません。Mapには、自由度を高めるために実装クラスが複数あり、まとめて使えることが逆に最適なものを選ぶときのネックとなるためです。ループに関しては特に、効率的なコードで記述量を減らす方法を知りたい方もいるでしょう。
この記事では、JavaでMapループ処理の最適化・効率化を目指すときに参考となるクラスやメソッドの活用手段、実際のソースコード組み立てやその手順などを解説します。
目次
1.JavaのMapループとは
JavaのMapは、「キー」と「値」のセットでデータを取り扱うインタフェースです。このMapは、「コレクションフレームワーク」という代表的なライブラリに含まれます。つまり、同じライブラリに入っているCollectionやListなどのインターフェースの仲間です。
Mapの意味
Map(マップ)という名称は、「マッピングする(マップする)」という言葉から取られています。意味は、「対応する値や要素を割り当てる」です。
そのため、語源の「地図」を意味せず、後から使われるようになった「対応させる」の意味がプログラミングでも使われています。JavaのMapで対応させるのは、キーと値のペアです。これをオブジェクトに格納して使います。
Mapはキーや値をループして使える
Mapで用意したデータは、ループで繰り返しの処理にも使えます。一般的には拡張for文が第一選択肢です。それ以外を使う場合は、「Iterator」や「Lambda式(ラムダ式)」でfor-each構文を使ってもループは可能となります。
Iteratorはループ時にMapの要素を「remove()」で削除できるのが特徴です。Lambda式は、簡易なソースコードで書けます。しかし、後述するように制限も多く、大量の要素を自由自在に処理したいときは拡張for文のほうが効率的な方法となります。
2.JavaのMapでループ処理の効率化に使われる実装クラス
以下では、Mapでループを使用する場合におすすめのクラスを4つ紹介します。効率化できるクラスと特徴を知ることで、Mapのループ処理の最適化に向いた実装クラスを選びやすくなるでしょう。
HashMap
HashMapは、Mapインターフェースのごく一般的な実装クラスです。Hash(ハッシュ)は、「ハッシュテーブル」のことで、素早くアクセスしてキーと値を格納・取得できる仕組みを持っています。その上、ループ時には「entrySet()」ですぐに取り出すことが可能です。
ただし、ループ時の取り出し順序の保証はなく、セーフスレッドでもありません。キーの重複も不可の仕様です。しかし、メモリ効率が高く、シンプルな取り出しで順序にこだわらないMap利用にはおすすめです。
LinkedHashMap
LinkedHashMapは、格納時の順番を守って取り出せる実装クラスです。HashMapと基本的に機能が同じです。ただし、HashMapと同様にLinkedHashMapもキーの重複はできません。順番が重要な際のループ利用におすすめです。
TreeMap
TreeMapはソートのできる実装クラスです。辞書や時刻など、取得したキーや値を順番に並べたいときに使います。基本、文字列や整数を昇順で並べ替えるため、上からabc、123順にという場合は特に便利です。
また、インスタンス化の際に、指定した順序を「Comparator」のインターフェースで変更できます。設定次第で「降順」や「文字の長さ順」などにすることも可能です。
順序指定の書き方は、以下のとおりです。
降順:
new TreeMap<>((a, b) -> b - a); |
文字の長さ順:
new TreeMap<>((s1, s2) -> s1.length() - s2.length()); |
ただし、TreeMapにnullは使用できず、スレッドセーフでもないため、使用時は注意が必要です。
ConcurrentHashMap
ConcurrentHashMapは、HashMapでは対応できなかったスレッドセーフに対応できる実装クラスです。スレッドセーフでは、複数のマルチスレッドからアクセスしてもデータを高速処理して、メモリが壊れない・例外が発生しないなどの安全な操作が保証されます。
他の実装クラスでは、複数のスレッドに同時アクセスした際にエラーや例外が出ることもあるでしょう。そのため、リアルタイムな処理やWEBのキャッシュ処理を開発・保守管理する際におすすめです。
関連記事
compareToメソッド徹底解説|文字列、数値、日付、カスタムオブジェクトの比較方法まで
Java「equals」とは?否定・複数条件の比較・文字列・nullの使い方や注意点を解説
3.JavaのMapでループする基本的な使い方と手順
以下の簡単なソースコード例をもとに、Mapをループするコードの作成手順を解説します。
import java.util.HashMap; import java.util.Map;
public static void main(String[] args) {
exampleMap.put("002", "トマト"); exampleMap.put("003", "ニンジン");
System.out.println("商品ID/番号: " + exampleEntryFor.getKey() + ", 野菜名: " + exampleEntryFor.getValue()); } } } |
手順1.Mapの宣言とインスタンス化
まずMapの作成に必要な宣言とインスタンス化を行います。先ほど紹介したHashMapなどの実装クラスを使用して、キーと値を設定する記述です。「Map<String, String>」で2つの文字列型を指定し、「exampleMap」でMapの変数を定義しています。
Map<String, String> exampleMap = new HashMap<>(); |
IDや番号を振る場合でも、基本的にはStringを指定します。Integer(整数のオブジェクト型)などを使わないのは、識別のための数字番号を計算に使う必要がないためです。また、文字列なら「001」と「1」のような番号の違いを明確に区別することも可能です。
インスタンス化が実装クラスの変更に簡単かつ重要な理由は、書き換えの便利さにあります。他の実装クラスを使用したい場合は、「HashMap」のコード箇所を「LinkedHashMap」「TreeMap」などに変えるだけで問題なく動作します。つまり、Mapは後でコードを修正することが非常に簡単です。
手順2.Mapのキーと値の用意
次に、Mapのキーと値を紐付けてputメソッドで変数「exampleMap」に格納します。今回は、商品ID/番号(001、002、003)と野菜名(商品名)をキーと値の組み合わせにし、「put()」メソッドでオブジェクトに入れています。
exampleMap.put("001", "キャベツ"); exampleMap.put("002", "トマト"); exampleMap.put("003", "ニンジン"); |
手順3.キーの指定で値を取り出す(1つだけ取得)
Javaでキーの指定から値を取り出すには、「get()」のメソッドを使って、キーを指定します。001ならそれに紐付けられた「キャベツ」が表示されます。
System.out.println(exampleMap.get("001")); |
手順4.拡張for文(foreach)によるループ
Mapのキーと値をセットして、値の取り出し方がわかったら、今度は拡張for文でそれをループして出力します。
拡張for文は、配列の場合に「for (型 変数名 : コレクション名)」と書きます。Mapの場合は、ループですべての値を取り出す場合にあわせて、これを「Map.Entry<K, V>型 Entryの変数名 : 格納したMapの変数名.entrySet()」と書きます。
Entryはペア(キーと値)を取り出すための型です。通常は下記のように「Map.Entry<String, String>」の型を書くのが基本です。
for (Map.Entry<String, String> exampleEntryFor : exampleMap.entrySet()) { |
後はループ処理内にキー取得の「getKey()」、値取得の「getValue()」を使って、「エントリ変数名.getKey()」などで出力内容を書くだけです。
System.out.println("商品ID/番号: " + exampleEntryFor.getKey() + ", 野菜名: " + exampleEntryFor.getValue()); |
注意点としては、格納したMapの変数名ではなく、Entryの変数名を指定します。この部分は処理内容を変えるだけでさまざまな活用が可能です。
以上が、JavaのMapでループする場合の基本的な組み立ての手順です。後述する応用や効率化のためにも、先に基本的なコードの組み立てやメソッドや変数の意味を理解することに重点を置いています。
関連記事
【Java】for文の使い方|拡張for文やforEach・配列/リスト操作・省略形を解説
4.JavaのMapループを効率的にする方法【応用編】
次に、基本を応用する形で、Mapのループを効率的に実装する方法です。以下に3つの応用パターンを紹介します。
拡張for文の一部省略
先のソースコード例では、型の指定が長く、型名がEntryやStringなどを入れて視覚的にも複雑化しています。
for (Map.Entry<String, String> exampleEntryFor : exampleMap.entrySet()) {} |
そこで、上記の型に代えて「var」を使用すると型をコンパイラに推論させて、省略することができます。
for (var exampleEntryFor : exampleMap.entrySet()) {} |
「Map.Entry<String, String>」を「var」に置き換えるだけでスッキリします。コードを短く効率化したいときに有効です。
順番に取り出す・ソート
使用したHashMapは、順番を規則通りに取り出す方法ではありません。そこで、「LinkedHashMap」や「TreeMap」を活用すると取り出す順番をコントロールできます。
Map<String, String> exampleMap = new HashMap<>(); |
Map<String, String> exampleMap = new LinkedHashMap<>(); |
Map<String, String> exampleMap = new TreeMap<>(); |
しかも使用するクラスの変更は、インスタンス化の部分を変更するだけです。全体のコードは変わりません。「LinkedHashMap」なら格納した順に、「TreeMap」なら昇順にソートするといった具合です。
TreeMapはキーがComparable(比較可能)である必要があり、StringやIntegerなどの基本型はすでにこのインターフェースを実装しています。また、カスタムのComparatorを指定することで、独自のソート順序を定義することも可能です。
最後の要素の取得とMap要素数や長さの確認
キーや値は、取り出して使うだけでなく、特定のキーと値だけを確認したり、Map全体の数を調べたりもできます。
まず、要素数は「size()」で簡単に取得できます。また、最後の要素の取得は、nullで変数を初期化する方法で可能です。
以下がソースコード例です。
import java.util.LinkedHashMap; import java.util.Map;
public static void main(String[] args) {
exampleMap.put("001", "キャベツ"); exampleMap.put("002", "トマト"); exampleMap.put("003", "ニンジン");
String lastKey = null; //nullで変数初期化 String lastValue = null;
lastKey = entry.getKey(); lastValue = entry.getValue(); }
} } |
5.JavaのMapをループするバリエーション
JavaのMapでループする方法は、拡張for文だけでなく、さまざまなものがあります。以下は、そのバリエーションとソースコード例です。状況に応じて使い分けることでコード記述やメモリの効率性が向上します。
MapをIteratorでループ
1つ目のバリエーションは、MapをIteratorでループすることです。特にデータを削除しながらループする場合におすすめです。なぜなら、Iteratorのメソッド「remove()」では、コレクションで原則不可とされているループ中の変更操作ができるからです。
もともとIteratorは、次の要素があるかを判定して、それがなくなるまで要素を取り出して操作を続けることができます。そのため、削除しても動作を停止せずに処理を実行するのです。
for (Map.Entry<String, String> exampleEntryFor : exampleMap.entrySet()) Iterator<Map.Entry<String, String>> exampleIterator = exampleMap.entrySet().iterator(); |
上記では、forではなく、Iteratorと内部処理の制御構文にwhileを使っています。また、Iterator型のジェネリクスのため「Iterator<>」への変更と、「next()」メソッドを使うために必要なオブジェクトの呼び出し「.iterator()」を最後に追加します。
これを押さえておかないと、元の拡張for文からソースコードを変更した際に、メソッドの呼び出しがなくエラーが出ます。
import java.util.HashMap; import java.util.Map; import java.util.Iterator;
public static void main(String[] args) {
exampleMap.put("001", "キャベツ"); exampleMap.put("002", "トマト"); exampleMap.put("003", "ニンジン");
Map.Entry<String, String> entry = exampleIterator.next(); System.out.println("商品ID/番号: " + entry.getKey() + ", 野菜名: " + entry.getValue());
exampleIterator.remove(); } }
} } |
上記のように、ifで特定条件のキーと値の削除もループ内に加えられます。
JavaのMapをLambda式でforEachメソッドでループ
Lambda式を使用する場合は、通常の拡張for文のループ記述よりも簡潔にコード文を書くことができます。下記の例で「.forEach()」のメソッドでは、「->」と書くことで1つずつ取り出して処理する記述を簡略化しています。
import java.util.HashMap; import java.util.Map;
public static void main(String[] args) {
exampleMap.put("001", "キャベツ"); exampleMap.put("002", "トマト"); exampleMap.put("003", "ニンジン");
exampleMap.forEach((key, value) -> { System.out.println("商品ID/番号: " + key + ", 野菜名: " + value); }); } } |
ただし、Lambda式のforEach文には、使用時にさまざまな制限やルールがあるため、注意が必要です。具体的な制限項目は以下です。
ループ完結が必須(停止や抜け出しNG)
nullへの対処
ループ中の削除不可
catchや例外処理がしにくい
拡張for文に比べて使いどころが限定されることがわかります。
JavaのStream APIをMapに使ったループ(Lambda式)
Lambda式でforEachメソッドでループする方法をさらに応用して、Stream APIでさらにコードを簡潔化することもできます。
import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors;
public static void main(String[] args) {
exampleMap.put("001", "キャベツ"); exampleMap.put("002", "トマト"); exampleMap.put("003", "ニンジン");
.stream() //Streamメソッドを呼び出してデータの流れに変換
System.out.println("商品ID/番号: " + entry.getKey() + ", 野菜名: " + entry.getValue()); }); } } |
StreamのAPI利用ではストリーム技術で使われる中間操作があることが特徴です。ストリームはデータを流れで受け取り、その流れを読み取ります。
Lambda式のforEachをループ処理する際に、「entrySet()」で取得したデータセット(キー、値の取得分)を「.stream() 」というメソッドの追加記述だけデータの流れとしてストリーム変換可能とする方法です。
変換後は、Lambda式「.forEach(entry ->」で1つずつループ処理すると短く書けます。この機能を使うためには、APIの「java.util.stream.Collectors」を最初にインポートすることが必要です。忘れずにimportを書きましょう。
関連記事
Java SE入門|最新バージョン、ダウンロード方法、Oracle製品ライセンスを解説
6.JavaでMapのループを使用する際のよくある質問
ここでは、JavaでMapのループを使用するときに疑問やわかりにくい部分について、よくある質問に回答します。
Mapがクラスやメソッドではなく、なぜインターフェース?
Mapがインタフェースの理由は、異なるメソッドの値でも受け取れて、コード設計の自由度の高さや変更のしやすさといったメリットがあるためです。
上述のソースコード例でも示したように、インスタンス化のクラス実装部分だけを変更して、プログラムの実行内容を変えられます。普通のクラスやメソッドでは、大部分を書き換える必要が出てくるため、そのあたりを踏まえた仕様です。
キーは重複できない?「キーは重複を許容しない」の意味は?
キーと値をセットする際に気になるのがキーや値の重複です。まず、「値」は基本的に重複可能です。値は同じでもキーによって管理されているので問題にはなりません。
しかし、「キー」は別で、重複してはダメです。「キーは重複を許容しない」ことを標準仕様でも公式Javaにも記載しており、もし重複すると格納の順番に関係なく、キーが値と一緒に上書きされます。
これはつまり、エラーが出るのではなく、「2つの同じキーが存在できない(=許容できない)」という意味です。
結局どのループ方法を使えばよい?
JavaでMapのループを使う場合に、正解はありません。よく使われるのは拡張for文ではありますが、短く書く場合にはラムダ式が優れています。
使うメソッドも状況に応じて一長一短です。どの特徴を重視して開発時にコードを組むのか、適切なものを使う人が都度判断する必要があります。
関連記事
Javaのプログラミングスクール10選【未経験・社会人必見】無料・安いスクールは?選び方のコツ解説
7.まとめ
今回は、JavaでMapをループして使用する際のポイントや効率化のための応用・バリエーションの事例を解説しました。Mapのループでは、Mapをセットして利用するまでの処理とループに使う処理の部分があり、どちらも適切に実装してクラスやメソッドを使い分けられる必要があります。
先の効率化した活用例などを踏まえて、Mapループ処理の最適化や効率化の方法を実践しましょう。
本記事が皆様にとって少しでもお役に立てますと幸いです。
「フリーランスボード」は、数多くのフリーランスエージェントが掲載するITフリーランスエンジニア・ITフリーランス向けの案件・求人を一括検索できるサイトです。
開発環境、職種、単価、稼働形態、稼働日数など様々な条件から、あなたに最適なフリーランス案件・求人を簡単に見つけることができます。
単価アップを目指す方や、自分の得意なスキルを活かせる案件に参画したい方は、ぜひ「フリーランスボード」をご利用ください。