1. 概要

このチュートリアルでは、KotlinのMapコレクションタイプを見ていきます。 まず、マップの定義とその特性から始めます。

次に、Kotlinでマップを作成する方法を調べます。 この記事の残りの部分では、エントリの読み取り、エントリの変更、データ変換などの一般的な操作について説明します。

2. Kotlinマップs

マップは、コンピュータサイエンスの一般的なデータ構造です。 これらは、他のプログラミング言語では辞書または連想配列としても知られています。 マップは、0個以上のキーと値のペアのコレクションを格納できます。

マップ内の各キーは一意であり、1つの値にのみ関連付けることができます。ただし、同じ値を複数のキーに関連付けることができます。

マップインターフェイスは、Kotlinの主要なコレクションタイプの1つです。 キーと値は任意のタイプとして宣言できます。 制限はありません:

interface Map<K, out V>

Kotlinでは、これらのキーと値のペアはエントリと呼ばれ、Entryインターフェイスで表されます。

interface Entry<out K, out V>

Mapインスタンスは不変であることに注意してください。 作成後にエントリを追加、削除、または変更することはできません。 変更可能なマップが必要な場合、Kotlinは MutableMap タイプ。作成後にエントリを変更できます。

interface MutableMap<K, V> : Map<K, V>

マップは、大量のデータがある場合でも、高速の読み取りおよび書き込みアクセスをサポートするため、プログラマーにとって強力なツールです。 これは、キーのルックアップと挿入が通常、O(1)操作であるハッシュを使用して実装されるためです。 Kotlinでマップを使用する方法を見てみましょう!

3. マップの構築

Kotlinには、標準ライブラリにいくつかのMap実装が含まれています。 2つの主要なタイプは、LinkedHashMapとHashMapです。 それらの主な違いは、LinkedHashMapがエントリを反復処理するときに挿入順序を維持することです。 Kotlinの他のクラスと同様に、デフォルトのコンストラクターを使用してそれらをインスタンス化できます。

val iceCreamInventory = LinkedHashMap<String, Int>()
iceCreamInventory["Vanilla"] = 24

ただし、Kotlinは、CollectionsAPIを介してマップを作成するためのより効率的な方法を提供します。 次にこれらを見てみましょう。

3.1. ファクトリ機能

1回の操作でMapインスタンスを宣言して設定するには、mapOfおよびmutableMapOf関数を使用します。 それらは、varargsとして渡すことができるペアのリストを受け入れます。 また、「 to」中置演算子を使用して、これらのペアをオンザフライで作成します。

val iceCreamInventory = mapOf("Vanilla" to 24, "Chocolate" to 14, "Rocky Road" to 7)

mapOf関数は不変のMap typeを返し、mutableMapOfMutableMapを返します。 後者は、作成後にエントリを変更する必要がある場合にのみ使用する必要があります。

3.2. Kotlin機能APIの使用

Kotlinの標準ライブラリには、非常に簡潔な方法でマップを生成できる便利なAPIが多数付属しています。たとえば、IceCreamShipmentオブジェクトのリストがあるとします。 各IceCreamShipmentには、フレーバーと数量のプロパティがあります。

val shipments = listOf(
  IceCreamShipment("Chocolate", 3),
  IceCreamShipment("Strawberry", 7),
  IceCreamShipment("Vanilla", 5),
  IceCreamShipment("Chocolate", 5),
  IceCreamShipment("Vanilla", 1),
  IceCreamShipment("Rocky Road", 10),
)

このリストから在庫のマップを生成したいと思います。 これを行う一般的な方法は、リストを反復処理することです。 出荷ごとに、そのフレーバーのマップエントリが作成または更新されます。

val iceCreamInventory = mutableMapOf<String, Int>()

for (shipment in shipments){
    val currentQuantity = iceCreamInventory[shipment.flavor] ?: 0
    iceCreamInventory[shipment.flavor] = currentQuantity + shipment.quantity
}

この実装は機能しますが、このマップを生成するためのより慣用的な方法は、Kotlinの機能APIを使用することです。

val iceCreamInventory = shipments
  .groupBy({ it.flavor }, { it.quantity })
  .mapValues { it.value.sum() }

groupBy を使用してフレーバーをその数量に関連付け、次にmapValues関数を使用して数量のリストを単一の合計に減らします。 リストに各キー(この場合はアイスクリームフレーバー)に複数のエントリがないことがわかっている場合は、代わりにmapまたはassociateBy関数を使用できます。

使用していることがわかった場合 MutableMap 最初にデータを入力するだけで、代わりにKotlinの機能APIを使用して生成するより良い方法がよくあります一般に、Kotlinは、コレクションをマップに変換するなどの一般的な目標を達成するための多くの便利な方法を提供します。

4. マップ全体へのアクセス

get メソッドを使用して、マップから値を取得します。 Kotlinでは、getメソッドの省略形として角かっこ表記を使用することもできます。

val map = mapOf("Vanilla" to 24)

assertEquals(24, map.get("Vanilla"))
assertEquals(24, map["Vanilla"])

マップにキーが存在しない場合のデフォルトのアクションを定義するいくつかのゲッターメソッドがあります。 指定されたキーが見つからない場合、getValueメソッドは例外をスローします。

assertThrows(NoSuchElementException::class.java) { map.getValue("Banana") }

getOrElse メソッドは、キーがマップ上にないときに実行されるラムダ関数を受け入れます。 ラムダの最後のステートメントは、戻り値としても使用されます。

assertEquals(0, map.getOrElse("Banana", { print("Warning: Flavor not found in map"); 0 }))

最後に、 getOrDefault メソッドは、キーが存在しない場合、提供されたデフォルト値を返します。

assertEquals(0, map.getOrDefault("Banana", 0))

5. エントリの追加と更新

MutableMap、を使用している場合は、putメソッドを使用して新しいエントリを追加できます。 角かっこ表記を省略形として再び使用できます。

val iceCreamSales = mutableMapOf<String, Int>()

iceCreamSales.put("Chocolate", 1)
iceCreamSales["Vanilla"] = 2

putAll メソッドを使用して複数のエントリを追加することもできます。このメソッドは、ペアのコレクションを受け入れてマップに追加します。 または、plus-assign演算子(+ =)を使用して、あるマップから別のマップにすべてのエントリを追加することもできます。

iceCreamSales.putAll(setOf("Strawberry" to 3, "Rocky Road" to 2))
iceCreamSales += mapOf("Maple Walnut" to 1, "Mint Chocolate" to 4)

キーがマップにすでに存在する場合、上記のすべてのメソッドが現在の値を上書きすることに注意してください。 代わりにエントリを更新する場合は、mergeメソッドを使用するのが最善の方法です。 例えば:

val iceCreamSales = mutableMapOf("Chocolate" to 2)
iceCreamSales.merge("Chocolate", 1, Int::plus)
assertEquals(3, iceCreamSales["Chocolate"])

merge メソッドは、キー、値、および再マッピング関数を受け入れます。 再マッピング関数は、キーがすでに存在する場合に古い値と新しい値をマージする方法を定義します。 アイスクリームの販売の場合は、単にそれらを足し合わせたいだけです。

6. エントリの削除

可変マップは、エントリを削除するためのメソッドも提供します。 remove メソッドは、マップから削除するキー引数を受け入れます。 キーが存在しない場合、removeを呼び出しても例外はスローされません。 オプションで、マイナス割り当て(-=)演算子を使用して、同じ操作を実行できます。

val map = mutableMapOf("Chocolate" to 14, "Strawberry" to 9)

map.remove("Strawberry")
map -= "Chocolate"
assertNull(map["Strawberry"])
assertNull(map["Chocolate"])

MutableMap インターフェイスは、マップのすべてのエントリを一度に削除するclearメソッドも定義します。

7. マップの変換

Kotlinの他のコレクションタイプと同様に、アプリケーションのニーズに合わせてマップを変換する方法はたくさんあります。 いくつかの便利な操作を見てみましょう。 以下に示すすべての例で、これはInventoryマップの初期データです。

val inventory = mutableMapOf(
  "Vanilla" to 24,
  "Chocolate" to 14,
  "Strawberry" to 9,
)

7.1. フィルタリング

Kotlinは、マップにいくつかのフィルターメソッドを提供します。 入力キーまたは値でフィルタリングするために、それぞれ filterKeysおよびfilterValues、があります。 両方でフィルタリングする必要がある場合は、filterメソッドがあります。 残りの量でアイスクリームの在庫をフィルタリングする例を次に示します。

val lotsLeft = inventory.filterValues { qty -> qty > 10 }
assertEquals(setOf("Vanilla", "Chocolate"), lotsLeft.keys)

filterValues メソッドは、指定された述語関数をマップ内のすべてのエントリの値に適用します。 フィルタリングされたマップは、述語条件に一致するエントリのコレクションです。

この条件に一致しないエントリを除外したい場合は、代わりにfilterNotメソッドを使用できます。

7.2. マッピング

map メソッドは、すべてのエントリを別のものに変換する変換関数を受け入れます。 マップされた値のリストを返します。

val asStrings = inventory.map { (flavor, qty) -> "$qty tubs of $flavor" }

assertTrue(asStrings.containsAll(setOf("24 tubs of Vanilla", "14 tubs of Chocolate", "9 tubs of Strawberry")))
assertEquals(3, asStrings.size)

ここでは、 map メソッドを使用して、現在の在庫を説明する文字列のリストを生成しました。

7.3. forEachを使用する

最後の例として、すでに学習した内容を使用して、forEachメソッドを紹介します。 forEachメソッドは、指定されたマップの各エントリに対してアクションを実行します。出荷を受け取り、アイスクリームを販売した1日後、ストアの在庫マップを更新する必要があります。  sales マップのすべてのエントリを減算し、 shipments マップのすべてのエントリを追加して、各フレーバーの数量を更新します。

val sales = mapOf("Vanilla" to 7, "Chocolate" to 4, "Strawberry" to 5)

val shipments = mapOf("Chocolate" to 3, "Strawberry" to 7, "Rocky Road" to 5)

with(inventory) {
    sales.forEach { merge(it.key, it.value, Int::minus) }
    shipments.forEach { merge(it.key, it.value, Int::plus) }
}

assertEquals(17, inventory["Vanilla"]) // 24 - 7 + 0
assertEquals(13, inventory["Chocolate"]) // 14 - 4 + 3
assertEquals(11, inventory["Strawberry"]) // 9 - 5 + 7
assertEquals(5, inventory["Rocky Road"]) // 0 - 0 + 5

ここでは、 with scope function を使用して、コードを整理しています。 この例は、Kotlinの強力なAPIを使用して複雑な操作でも簡単に実行できることを示しています。

8. 結論

この記事では、Kotlinでマップを使用する方法を紹介しました。 マップは、効率的なコードを作成するために不可欠なツールです。 利用できる方法はたくさんあります。 ここで取り上げた以上のものです。 コードで最も適切なメソッドを使用していることを確認するために、常にドキュメントを参照する必要があります。

この記事で使用されているすべてのコードといくつかの追加の例は、GitHubからで入手できます。