Collections

可変コレクションおよび不変コレクション

Language

Scala のコレクションは、体系的に可変および不変コレクションを区別している。可変 (mutable) コレクションは上書きしたり拡張することができる。これは副作用としてコレクションの要素を変更、追加、または削除することができることを意味する。一方、不変 (immutable) コレクションは変わることが無い。追加、削除、または更新を模倣した演算は提供されるが、全ての場合において演算は新しいコレクションを返し、古いコレクションは変わることがない。

コレクションクラスの全ては scala.collection パッケージもしくは mutableimmutablegeneric のどれかのサブパッケージに定義されている。クライアントコードに必要なコレクションのクラスのほどんどには可変性に関して異なる特性を持つ 3つの形態が定義されおり、ぞれぞれ scala.collectionscala.collection.immutable、か scala.collection.mutable のパッケージに存在する。

scala.collection.immutable パッケージのコレクションは、誰にとっても不変であることが保証されている。 そのようなコレクションは作成後には一切変更されることがない。したがって、異なる時点で何回同じコレクションの値にアクセスしても常に同じ要素を持つコレクションが得られることに依存できる。

scala.collection.mutable パッケージのコレクションは、コレクションを上書き変更する演算がある。 だから可変コレクションを扱うということは、どのコードが、何時どのコレクションを変更したのかということを理解する必要があることを意味する。

scala.collection パッケージのコレクションは、可変か不変かのどちらでもありうる。例えば collection.IndexedSeq[T] は、collection.immutable.IndexedSeq[T]collection.mutable.IndexedSeq[T] 両方の親クラスだ。一般的に、scala.collectionパッケージの基底コレクションは不変コレクションと同じインターフェイスを定義し、scala.collection.mutable パッケージ内の可変コレクションは、副作用を伴う変更演算を不変インターフェイスに加える。

基底コレクションと不変コレクションの違いは、不変なコレクションのクライアントは、他の誰もコレクションを変更しないという保証があるのに対し、基底コレクションのクライアントは自分ではコレクションを変更しなかったという約束しかできない。たとえ静的な型がコレクションを変更するような演算を提供していなくても、実行時の型は他のクライアントが手を加えることができる可変コレクションである可能性がある。

デフォルトでは Scala は常に不変コレクションを選ぶ。たとえば、scala パッケージのデフォルトのバインディングにより、なんの接頭辞や import もなくただ Set と書くと不変な集合 (set) が返ってき、Iterable と書くと不変で反復可能 (iterable)なコレクションが返ってくる。可変なデフォルト実装を取得するには、collection.mutable.Set または collection.mutable.Iterable と明示的に記述する必要がある。

可変と不変の両方のバージョンのコレクションを使用する場合に便利な慣例は collection.mutable パッケージだけをインポートすることだ。

import scala.collection.mutable

これにより、接頭辞なしの Set は不変なコレクションを参照するのに対し、mutable.Set は可変版を参照する。

コレクション階層内の最後のパッケージは collection.generic だ。 このパッケージには、コレクションを実装するための基本的なパーツが含まれている。 コレクションクラスがいくつかの演算を generic 内のクラスに委譲することはよくあるが、 フレームワークのユーザーが generic 内のクラスが必要になることは普通はありえない。

利便性と後方互換性のために、いつくかの重要な型は scala パッケージ内に別名を定義してあるため、インポート無しで単純な名前でコレクションを使うことができる。List 型が良い例で、以下の名前でもアクセスすることができる

scala.collection.immutable.List   // 定義元
scala.List                        // scala パッケージのエイリアス経由
List                              // scala._  パッケージは
                                  // 常に自動的にインポートされるため

エイリアスされているその他の型は次のとおり: TraversableIterableSeqIndexedSeqIteratorStreamVectorStringBuilderRange

次の図は scala.collection パッケージ内の全てのコレクションを示す。 これらはすべて、高レベルの抽象クラスやトレイトで一般に可変と不変の両方の実装を持っている。

次の図は scala.collection.immutable パッケージ内の全てのコレクションを示す。

そして、次の図は scala.collection.mutable パッケージ内の全てのコレクションを示す。

(以上三つ全ての図は decodified.com の Matthias によって生成された。)

コレクションAPIの概要

最も重要なコレクションクラスは上の図に示されている。 これらの全てのクラスに共通な部分が沢山ある。 例えば、全てのコレクションは、クラス名を書いた後で要素を書くという統一された構文で作成することができる:

Traversable(1, 2, 3)
Iterable("x", "y", "z")
Map("x" -> 24, "y" -> 25, "z" -> 26)
Set(Color.red, Color.green, Color.blue)
SortedSet("hello", "world")
Buffer(x, y, z)
IndexedSeq(1.0, 2.0)
LinearSeq(a, b, c)

特定のコレクションの実装にもこの原則が適用される:

List(1, 2, 3)
HashMap("x" -> 24, "y" -> 25, "z" -> 26)

これらのコレクションは、toString を呼び出すと上の表記方法で表示される。

すべてのコレクションが Traversable によって提供される API をサポートするが、理にかなうところでは型を特殊化している。 たとえば、Traversable クラスの map メソッドは別の Traversable を戻り値として返すが、結果の型はサブクラスでオーバーライドされる。 たとえば、Listmap を呼び出しても再び List が返ってき、Setmap を呼び出すと Set が返ってくる、といういう具合だ。

scala> List(1, 2, 3) map (_ + 1)
res0: List[Int] = List(2, 3, 4)
scala> Set(1, 2, 3) map (_ * 2)
res0: Set[Int] = Set(2, 4, 6)

コレクションライブラリ中のあらゆる所で実装されているこの振る舞いは戻り値同型の原則 と呼ばれる。

コレクションの階層のクラスのほとんどは基底、不変、可変の3種類とも存在する。 唯一の例外は、可変コレクションにのみ存在する Buffer トレイトだ。

これより、これらのクラスを一つづつ見ていく。