コレクション概要
Kotlinの標準ライブラリは コレクション を管理する為の十分な仕組みを提供しています。 コレクションとは、要素数が可変(0の場合もある)の要素のグループで、 解決したい問題に重要で良く使われるものです。
コレクションはほとんどのプログラム言語において共通の概念なので、 もしJavaとかPythonといった他の言語のコレクションに良く慣れ親しんでいるのなら、 このイントロダクションはスキップして先に続く各詳細に関してのセクションに進んでしまって良いでしょう。
コレクションは通常、同じ型のたくさんのオブジェクト(0個の場合もあるけれど)を保持します。 コレクションの中のオブジェクトは要素とかアイテムと呼ばれます。 例えば、学部の生徒全員、などはコレクションを形成し、 そのコレクションを使って平均の年齢を計算したり出来ます。
Kotlinでは、以下のコレクションの型が関連しています:
- リスト 順番のある要素のコレクションで要素にインデックスでアクセスする - インデックスとはその位置を表す整数値 リストには同じ要素が複数回現れ得る。リストの例としては電話番号などが考えられる。電話番号は数字のグループで、順番が大切で、同じ番号が出てくる場合がある。
- セット ユニークな要素のコレクション。数学の集合(セット)と対応した概念。同じオブジェクトが繰り返し出てくる事が無い、オブジェクトのグループ。 一般的に、セットの要素の順番は重要では無い。例えば(訳注:10枚組とかの)宝くじの番号などはセットを形成する。 それらの番号はユニークで、(くじの)順番は重要では無い。
- マップ (または 辞書 ) キーと値のペアの集まり。キーはユニークで、各キーはちょうど一つの値に対応(map)している。 値は重複していても良い。マップはオブジェクト間の論理的なつながりを格納するのに便利。 例えば社員IDとその役職など。
Kotlinではコレクションの操作を、格納されているオブジェクトの型によらず同一のやり方で行えます。
言い換えると、String
をString
のリストに追加するのと同じように、Int
をInt
のリストに追加出来るし、
Int
をユーザー定義のクラスに置き換えても同様です。
つまり、Kotlinの標準ライブラリはコレクションを作成、生成、管理するのに、
ジェネリックなインターフェース、クラス、関数を提供していると言えます。
コレクションのインターフェースや関連する関数などは
kotlin.collections
パッケージに置かれています。
その中身の概要を見ていきましょう。
配列はコレクションの型ではありません。詳細は配列を見てください。
コレクションの種類
Kotlinの標準ライブラリは基本的な種類のコレクションの実装を提供します: セット、リスト、マップです。 1ペアのインターフェースが各種類のコレクションを表します:
- 読み取り専用インターフェース:コレクションの要素へのアクセスを提供する
- ミュータブルインターフェース:読み取り専用のインターフェースを継承してさらに書き込みオペレーションを追加する: 要素の追加、削除、更新など
ミュータブルなコレクションの中身を変更するために、var
である必要は無い事には注意が必要です。
書き込みオペレーションは同じミュータブルなコレクションのオブジェクトを変更するだけなので、
リファレンスは変わりません。
val
のコレクションに再代入しようとすればコンパイルエラーにはなりますが。
読み取り専用コレクションは共変です。
それの意味する所は、Shape
を継承したRectangle
クラスがあれば、
List<Shape>
を要求するすべての場所でList<Rectanble>
が使える、という事です。
言い換えると、コレクションの型は要素の型と同じ型継承関係となる、という事です。
マップは値の型に対して共変ですが、キーの型についてはそうではありません。
一方、ミュータブルなコレクションは共変ではありません: もしそうなら、実行時失敗を起こしてしまいますから。
もしMutableList<Rectangle>
がMutableList<Shape>
のサブタイプなら、
そのほかのShape
(例えばCircle
とか)を挿入出来てしまい、
Rectangle
という型引数に違反する事になってしまいます。
以下はKotlinのコレクションのインターフェースのダイアグラムです:
インターフェースとその実装をウォークスルーしていきましょう。
Collection
について学ぶなら、以下のセクションを読んで見てください。
List
、Set
、Map
について学ぶなら、それぞれのセクションを読むか、Kotlin Developer AdvocateのSebastian Aignerの以下の動画を見ると良いでしょう:
Collection
Collection<T>
はコレクションの継承階層のルートに位置します。
このインターフェースは読み取り専用コレクションに共通な振る舞いを表します:サイズの取得、特定の要素が入っているかのチェック、などなどです。
Collection
はIterable<T>
を継承しています。Iterable<T>
は要素のイテレーションのオペレーションを定義するインターフェースです。
異なる種類のコレクションを同時に引き受けるようなパラメータの型としてCollection
型を使う事が出来ます。
より具体的な場合なら、
Collection
の継承先を使うと良いでしょう:
List
と Set
です。
MutableCollection<T>
は、Collection
に書き込みオペレーションを追加したものです:書き込みというのはadd
とかremove
などです。
List
List<T>
は要素を指定された順番に格納して、
その要素へのインデックスによるアクセスを提供します。
インデックスは0から始まり(0は最初の要素を指します)、lastIndex
までにわたります。lastIndex
は(list.size-1)
です。
リストの要素は(nullも含め)、重複しえます。 リストは等しいオブジェクトをいくらでも保持する事が出来るし、 一つのオブジェクトが何度も現れても問題ありません。 二つのリストは、サイズが同じで各位置にある要素がstructurally equalならイコールだとみなされます。
MutableList<T>
はList
に、
さらにリスト固有の書き込みオペレーション、例えば特定の位置に要素を足したり特定の位置の要素を削除したり、といったものを追加したものです。
見ての通り、リストは幾つかの点でとても配列に似ています。 しかし、一つ重要な違いがあります: 配列のサイズは初期化の時点で決まり、決して変わりません。 一方、リストは最初に決められたサイズはありません。 リストのサイズは書き込みオペレーション、つまり要素を追加したり更新したり削除したりする事に伴って変わっていきます。
Kotlinでは、MutableList
のデフォルトの実装はArrayList
で、
これはサイズ変更可能な配列のようなものとみなせるでしょう。
Set
Set<T>
はユニークな要素を格納します。
順番は一般的には定義されません。null
要素もユニークです:
Set
はたかだか一つしかnull
を含む事が出来ません。
二つのセットは、サイズが等しく、片方のセットの各要素が、それと等しい要素をもう一方のセットに持つならイコールだとみなされます。
MutableSet
はSet
にMutableCollection
の書き込みオペレーションを加えたものです。
MutableSet
のデフォルトの実装はLinkedHashSet
です。
これは要素の挿入の順番を保存します。
だから順番に依存するような関数、first()
とか last()
も、
予測可能な結果を返します。
それとは代替的な実装としては、HashSet
があります。
こちらは要素の順番には何も保証していません。
このコレクションに対してさきほどのような関数を呼んでも、
結果は予測不能です。
しかしながらHashSet
の方が同じ数の要素を格納するのに少ないメモリで済みます。
Map
Map<K, V>
はCollection
を継承していません。
しかしこれもKotlinのコレクションの一種です。
Map
はキーと値のペア(エントリとも言う)を格納します。
キーはユニークですが、異なるキーが同じ値を指しても構いません。
Map
インターフェースはMap
固有の関数、例えばキーによる値へのアクセスやキーの検索や値の検索など、を提供します。
同じペアからなるマップは、順番がどうであれイコールと見なされます。
MutableMap
はMap
にマップの書き込みオペレーションを加えたものです。
書き込みオペレーションは新しい キーと値のペアを追加したり、あるキーに関連付けされた値を更新したり、などです。
MutableMap
のデフォルトの実装はLinkedHashMap
です。
これはマップをイテレートする時に挿入した順番を保ちます。
一方、その代替的な実装である HashMap
は要素の順番について何も規定していません。