現在プログラマーとして勤務しているのですが、直近の業務でJavaプログラムの改修を行うことがあり、
その際にMapの記法について知識不足の部分があったため作業が空回りしてしまったことがありました。
調べたらすぐに解決しましたが、基礎中の基礎と言える部分を忘れてしまっていたため、
「ここまで落ちたか…」と落胆しまして…、復習というものは大事だと改めて痛感しました。
さて、せっかくなので、
今回は以前勉強していたScalaの復習としてScalaのMapについてJavaと比較しながら記法・仕様を再確認していきたいと思います。
あまり深くまでは突っ込まず書いていきたいので、
JavaやScala初学者、JavaプログラマーだけどScala勉強してみたいという方に見ていただければ幸いです。
そもそもMapとは
そもそもMapとはどういったものなのか、どんな時に使われるのかを軽く説明していきたいと思います。
なお、本項ではあくまでJavaをベースとして説明していきます。
Mapとは、ListやSet等と合わせて「コレクション」と呼ばれ、
「キー(key)」と「値(value)」という2つの要素をペアにして格納することができるデータ構造が特徴です。
Listの場合は値に対してインデックス番号が自動採番されますが、Mapの「キー」には文字列等を格納することができるため、
「キー」を使用して「値」を抽出したい場合等に有用であると言えます。以下にJavaのMapを使った簡単な例を記載します。
例1:ユーザが入力した商品の1個当たりの値段を抽出するメソッド
import java.util.HashMap;
import java.util.Map;
//クラス等は省略
//引数(ユーザからの入力値)に指定された商品名の値段を返すメソッド
//※今回はinputKey = "りんご"であったと仮定する。
int priceSelect(String inputKey) {
//("商品名","1個当たりの値段")を格納するMapを宣言
Map<String, int> priceMap = new HashMap<>();
//各商品名とその1個当たりの値段をMapへ格納
priceMap.put("りんご", 150);
priceMap.put("みかん", 100);
priceMap.put("バナナ", 200);
return priceMap.get(inputKey);
//※inputKey = "りんご"のため、戻り値(1個当たりの値段)として「150」が抽出される。
}
Scalaのコレクションライブラリの特徴
ScalaにおけるMapを説明する前に、
Scalaのコレクションにおける共通の特徴としてimmutable(不変)とmutable(可変)について少々触れておきます。
Scalaのコレクションでは、
一度値を格納すると変更ができないimmutableなコレクションと、
Javaと同様に変更可能であるmutableなコレクションが存在します。
また、これらを明示的に定義しない場合、宣言したコレクションはimmutableとして作成されます。
例えばJavaのMapの場合、Java8から導入されたreplaceメソッドを使用することで指定したキーの値を変更することができますが、
Scalaの場合はmutableと明示した上で値を変更する必要があります。
ScalaのMapにおける記法・特徴
まず、ScalaのMapにおける代表的な記法を以下に記載します。
//Mapを定義
val nulMap = Map[String,Int]() //※1
val conMap = Map("abc" -> 123) //※1
val mutableMap = Map("abc" -> 123)
//値の取得
conMap("abc") //123
conMap.get("abc") //Some(123)
//値の追加
val addMap1 = nulMap + ("edf" -> 456) //※2
val addMap2 = nulMap ++ Map("edf" -> 456) //※3
nulMap += "edf" -> 456
nulMap.put("edf" , 456)
//値の削除
val delMap = conMap - "abc"
//値の変更(mutableなMapを定義した場合)
val updMap = mutableMap updated("abc" -> 456)
updMap("abc") //456
//全てのキーを取得
conMap.keys //Set("abc")
//全ての値を取得
conMap.values //Iterable(123)
//要素数の取得
conMap.size //1
※1…2行目でMapの定義時に型を宣言していますが、型を宣言しなかった場合に自動的に型が[Nothing,Nothing]と定義されます。これは明示的に型を定義しなかった場合に格納されている値から型を自動判別する「型推論」によるものであり、逆に言えば、3行目では型を定義しなくても格納されているキー・値からMap[String,Int]として定義しています。
※2,3…これらを使い分ける大きな基準として、「追加するキー・値の型」と「追加されるMapの型」が一致していない場合は※3の記法を使用しなければいけません。例えばMap[String,Int](“abc” -> 123,”edf” -> 456)にMap(789 -> “ghi”)というデータを追加する場合、※2の記法では型不一致としてエラーとなりますが、※3の記法であれば追加できます。ただし、追加後のMapの型は[Any,Any]となってしまう点には注意が必要です。
上記での記法を用いてScalaで例1を記述した場合、以下のようになります。
例2:例1をScalaで記載した場合
//priceSelectメソッドにてmutableなMapを宣言するためにimport
//immutableなMapを使用する場合はimportは不要
import scala.collection.mutable
//クラス等は省略
//引数に指定された商品名の値段を返すメソッド
//※今回はinputKey = "りんご"であったと仮定する。
def priceSelect(inputKey:String):Int = {
//("商品名","1個当たりの値段")を格納するMapを宣言
//mutableなMapを宣言…※4
val priceMap = Map[String,Int]()
//各商品名とその1個当たり値段をMapへ格納
priceMap.put("りんご",150)
priceMap.put("みかん",100)
priceMap.put("バナナ",200)
priceMap(inputKey)
//※inputKey = "りんご"のため、戻り値(1個当たりの値段)として「150」が抽出される。
}
//上記メソッドの簡略化ver.
def priceSelectIns(inputKey:String):Int = {
//("商品名","1個当たりの値段")を格納するMapを宣言(ここはimmutableでも可…※4)
//各商品名とその1個当たり値段をMapへ格納
val priceMap = Map("りんご" -> 150,"みかん" -> 100,"みかん" -> 200)
priceMap(inputKey)
}
priceSelectメソッドは例1のJava処理をそのままScalaに置き換えたもの、
priceSelectInsメソッドはそれをScalaの特徴を用いて極力簡単な記述にしたものになります。
実際に使用する場合はpriceSelectInsメソッドのように値の格納までを1行で簡潔に済ますことは難しいと思われますが、
型推論等によって幾分か記述量を減らすことは可能になるかと思います。
※4…priceSelectメソッドのようにputで後からMapに要素を追加する場合、mutableにしなければいけない点に注意。
まとめ
ScalaのMapにおける記法等について説明してきましたが、大きな特徴としては以下が挙げられるかと思います。
- immutableとmutableの2種類が存在する
- 型推論により、記述方法によってはMapの型定義が不要となる
これらをメリット・デメリットと取るかは状況によりけりであるかと思いますが、
特に1.に関してMapにputで値を追加する場合はmutableを明示しなければいけない点は、
Javaを使用する上では考え難い機能であり罠に陥りやすいポイントであると感じました。