Edit Page

コレクション概要

Kotlinの標準ライブラリは コレクション を管理する為の十分な仕組みを提供しています。 コレクションとは、要素数が可変(0の場合もある)の要素のグループで、 解決したい問題に重要で良く使われるものです。

コレクションはほとんどのプログラム言語において共通の概念なので、 もしJavaとかPythonといった他の言語のコレクションに良く慣れ親しんでいるのなら、 このイントロダクションはスキップして先に続く各詳細に関してのセクションに進んでしまって良いでしょう。

コレクションは通常、同じ型のたくさんのオブジェクト(0個の場合もあるけれど)を保持します。 コレクションの中のオブジェクトは要素とかアイテムと呼ばれます。 例えば、学部の生徒全員、などはコレクションを形成し、 そのコレクションを使って平均の年齢を計算したり出来ます。

Kotlinでは、以下のコレクションの型が関連しています:

  • リスト 順番のある要素のコレクションで要素にインデックスでアクセスする - インデックスとはその位置を表す整数値 リストには同じ要素が複数回現れ得る。リストの例としては電話番号などが考えられる。電話番号は数字のグループで、順番が大切で、同じ番号が出てくる場合がある。
  • セット ユニークな要素のコレクション。数学の集合(セット)と対応した概念。同じオブジェクトが繰り返し出てくる事が無い、オブジェクトのグループ。 一般的に、セットの要素の順番は重要では無い。例えば(訳注:10枚組とかの)宝くじの番号などはセットを形成する。 それらの番号はユニークで、(くじの)順番は重要では無い。
  • マップ (または 辞書 ) キーと値のペアの集まり。キーはユニークで、各キーはちょうど一つの値に対応(map)している。 値は重複していても良い。マップはオブジェクト間の論理的なつながりを格納するのに便利。 例えば社員IDとその役職など。

Kotlinではコレクションの操作を、格納されているオブジェクトの型によらず同一のやり方で行えます。 言い換えると、StringStringのリストに追加するのと同じように、IntIntのリストに追加出来るし、 Intをユーザー定義のクラスに置き換えても同様です。 つまり、Kotlinの標準ライブラリはコレクションを作成、生成、管理するのに、 ジェネリックなインターフェース、クラス、関数を提供していると言えます。

コレクションのインターフェースや関連する関数などは kotlin.collectionsパッケージに置かれています。

その中身の概要を見ていきましょう。

配列はコレクションの型ではありません。詳細は配列を見てください。

コレクションの種類

Kotlinの標準ライブラリは基本的な種類のコレクションの実装を提供します: セット、リスト、マップです。 1ペアのインターフェースが各種類のコレクションを表します:

  • 読み取り専用インターフェース:コレクションの要素へのアクセスを提供する
  • ミュータブルインターフェース:読み取り専用のインターフェースを継承してさらに書き込みオペレーションを追加する: 要素の追加、削除、更新など

ミュータブルなコレクションの中身を変更するために、varである必要は無い事には注意が必要です。 書き込みオペレーションは同じミュータブルなコレクションのオブジェクトを変更するだけなので、 リファレンスは変わりません。 valのコレクションに再代入しようとすればコンパイルエラーにはなりますが。

fun main() { //sampleStart val numbers = mutableListOf("one", "two", "three", "four") numbers.add("five") // これはOK println(numbers) //numbers = mutableListOf("six", "seven") // コンパイルエラー //sampleEnd }

読み取り専用コレクションは共変です。 それの意味する所は、Shapeを継承したRectangleクラスがあれば、 List<Shape>を要求するすべての場所でList<Rectanble>が使える、という事です。 言い換えると、コレクションの型は要素の型と同じ型継承関係となる、という事です。 マップは値の型に対して共変ですが、キーの型についてはそうではありません。

一方、ミュータブルなコレクションは共変ではありません: もしそうなら、実行時失敗を起こしてしまいますから。 もしMutableList<Rectangle>MutableList<Shape>のサブタイプなら、 そのほかのShape(例えばCircleとか)を挿入出来てしまい、 Rectangleという型引数に違反する事になってしまいます。

以下はKotlinのコレクションのインターフェースのダイアグラムです:

Collection interfaces hierarchy

インターフェースとその実装をウォークスルーしていきましょう。 Collectionについて学ぶなら、以下のセクションを読んで見てください。 ListSetMapについて学ぶなら、それぞれのセクションを読むか、Kotlin Developer AdvocateのSebastian Aignerの以下の動画を見ると良いでしょう:

Collection

Collection<T>はコレクションの継承階層のルートに位置します。 このインターフェースは読み取り専用コレクションに共通な振る舞いを表します:サイズの取得、特定の要素が入っているかのチェック、などなどです。 CollectionIterable<T>を継承しています。Iterable<T>は要素のイテレーションのオペレーションを定義するインターフェースです。 異なる種類のコレクションを同時に引き受けるようなパラメータの型としてCollection型を使う事が出来ます。 より具体的な場合なら、 Collectionの継承先を使うと良いでしょう: ListSetです。

fun printAll(strings: Collection<String>) { for(s in strings) print("$s ") println() } fun main() { val stringList = listOf("one", "two", "one") printAll(stringList) val stringSet = setOf("one", "two", "three") printAll(stringSet) }

MutableCollection<T> は、Collectionに書き込みオペレーションを追加したものです:書き込みというのはaddとかremoveなどです。

fun List<String>.getShortWordsTo(shortWords: MutableList<String>, maxLength: Int) { this.filterTo(shortWords) { it.length <= maxLength } // throwing away the articles val articles = setOf("a", "A", "an", "An", "the", "The") shortWords -= articles } fun main() { val words = "A long time ago in a galaxy far far away".split(" ") val shortWords = mutableListOf<String>() words.getShortWordsTo(shortWords, 3) println(shortWords) }

List

List<T>は要素を指定された順番に格納して、 その要素へのインデックスによるアクセスを提供します。 インデックスは0から始まり(0は最初の要素を指します)、lastIndexまでにわたります。lastIndex(list.size-1)です。

fun main() { //sampleStart val numbers = listOf("one", "two", "three", "four") println("要素の数: ${numbers.size}") println("3番目の要素: ${numbers.get(2)}") println("4番目の要素: ${numbers[3]}") println("要素 \"two\" のインデックス ${numbers.indexOf("two")}") //sampleEnd }

リストの要素は(nullも含め)、重複しえます。 リストは等しいオブジェクトをいくらでも保持する事が出来るし、 一つのオブジェクトが何度も現れても問題ありません。 二つのリストは、サイズが同じで各位置にある要素がstructurally equalならイコールだとみなされます。

data class Person(var name: String, var age: Int) fun main() { //sampleStart val bob = Person("Bob", 31) val people = listOf(Person("Adam", 20), bob, bob) val people2 = listOf(Person("Adam", 20), Person("Bob", 31), bob) println(people == people2) bob.age = 32 println(people == people2) //sampleEnd }

MutableList<T>Listに、 さらにリスト固有の書き込みオペレーション、例えば特定の位置に要素を足したり特定の位置の要素を削除したり、といったものを追加したものです。

fun main() { //sampleStart val numbers = mutableListOf(1, 2, 3, 4) numbers.add(5) numbers.removeAt(1) numbers[0] = 0 numbers.shuffle() println(numbers) //sampleEnd }

見ての通り、リストは幾つかの点でとても配列に似ています。 しかし、一つ重要な違いがあります: 配列のサイズは初期化の時点で決まり、決して変わりません。 一方、リストは最初に決められたサイズはありません。 リストのサイズは書き込みオペレーション、つまり要素を追加したり更新したり削除したりする事に伴って変わっていきます。

Kotlinでは、MutableListのデフォルトの実装はArrayListで、 これはサイズ変更可能な配列のようなものとみなせるでしょう。

Set

Set<T>はユニークな要素を格納します。 順番は一般的には定義されません。null要素もユニークです: Setはたかだか一つしかnullを含む事が出来ません。 二つのセットは、サイズが等しく、片方のセットの各要素が、それと等しい要素をもう一方のセットに持つならイコールだとみなされます。

fun main() { //sampleStart val numbers = setOf(1, 2, 3, 4) println("要素の数: ${numbers.size}") if (numbers.contains(1)) println("1はセットに含まれている") val numbersBackwards = setOf(4, 3, 2, 1) println("これらのセットは等しい: ${numbers == numbersBackwards}") //sampleEnd }

MutableSetSetMutableCollectionの書き込みオペレーションを加えたものです。

MutableSetのデフォルトの実装はLinkedHashSetです。 これは要素の挿入の順番を保存します。 だから順番に依存するような関数、first() とか last()も、 予測可能な結果を返します。

fun main() { //sampleStart val numbers = setOf(1, 2, 3, 4) // LinkedHashSetがデフォルトの実装 val numbersBackwards = setOf(4, 3, 2, 1) println(numbers.first() == numbersBackwards.first()) println(numbers.first() == numbersBackwards.last()) //sampleEnd }

それとは代替的な実装としては、HashSetがあります。 こちらは要素の順番には何も保証していません。 このコレクションに対してさきほどのような関数を呼んでも、 結果は予測不能です。 しかしながらHashSetの方が同じ数の要素を格納するのに少ないメモリで済みます。

Map

Map<K, V>Collectionを継承していません。 しかしこれもKotlinのコレクションの一種です。 Mapキーと値のペア(エントリとも言う)を格納します。 キーはユニークですが、異なるキーが同じ値を指しても構いません。 MapインターフェースはMap固有の関数、例えばキーによる値へのアクセスやキーの検索や値の検索など、を提供します。

fun main() { //sampleStart val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1) println("全部のキー: ${numbersMap.keys}") println("全部の値: ${numbersMap.values}") if ("key2" in numbersMap) println("\"key2\"の値は: ${numbersMap["key2"]}") if (1 in numbersMap.values) println("値 1 はマップに有り") if (numbersMap.containsValue(1)) println("値 1 はマップに有り") // 前と同様 //sampleEnd }

同じペアからなるマップは、順番がどうであれイコールと見なされます。

fun main() { //sampleStart val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1) val anotherMap = mapOf("key2" to 2, "key1" to 1, "key4" to 1, "key3" to 3) println("マップは等しい: ${numbersMap == anotherMap}") //sampleEnd }

MutableMapMapにマップの書き込みオペレーションを加えたものです。 書き込みオペレーションは新しい キーと値のペアを追加したり、あるキーに関連付けされた値を更新したり、などです。

fun main() { //sampleStart val numbersMap = mutableMapOf("one" to 1, "two" to 2) numbersMap.put("three", 3) numbersMap["one"] = 11 println(numbersMap) //sampleEnd }

MutableMapのデフォルトの実装はLinkedHashMapです。 これはマップをイテレートする時に挿入した順番を保ちます。 一方、その代替的な実装である HashMapは要素の順番について何も規定していません。