kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

Kotlinのコレクションは「不変」と「可変」に分かれていない

Kotlinのコレクションライブラリに関する解説として、よく、

Kotlinのコレクションは不変と可変に分かれていて〜

といった形のものを見かけます。そして、その例として、

val list: List<Int> = listOf(1, 2, 3)
list.add(4) // コンパイルエラー

のようなコードが挙げられていたりします(このような解説を結構見かけるということで、特定の誰かを想定しているわけではありません)。しかし、これは誤りです。以下のコードを実行することで、Kotlinのコレクションの、一見「不変」っぽい型は単に「読み取り専用」の型でしかないことがわかります。

val list1: MutableList<Int> = mutableListOf(1, 2, 3)
val list2: List<Int> = list1
list1[0] = 4
println(list2) // list2が「不変」なら [1, 2, 3] になるはずだが、実際は [4, 2, 3]

最後の行のコメントに說明を書いてありますが、これが全てです。「可変」コレクションを「読み取り専用」の型に代入しても、「可変」コレクションの方を変更してしまうと、その変更は「読み取り専用」の型に代入したコレクションにも波及します。

一見、不変にみえるものが単なる読み取り専用でしかない(ことがある)ので、たとえば、以下のようなコードで

fun setParams(params: List<String>) {
    this.params = params
}

フィールドにうかつに読み取り専用コレクションを代入すると、あとでフィールドの値が変わってしまうことがあります。そのため、場合によっては、防御的コピーを作成する等の手段を講じる必要があるでしょう。

この誤解は本当に頻繁に見かけるので、注意しましょう。