1. 序章

このチュートリアルでは、Kotlinで不変のコレクションを作成する方法を見ていきます。

まず、不変性のタイプと、Kotlinが標準として提供するものについて説明します。 次に、 GoogleのGuavaライブラリを活用して、真に不変のコレクションを作成する方法を見ていきます。

別の方法として、Kotlin用のKotlinx不変コレクションライブラリも確認します。

2. 依存関係

不変のコレクションを作成する前に、Guavaと不変のコレクションライブラリをインポートする必要があります。

2.1. Maven

<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-collections-immutable -->
<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-collections-immutable</artifactId>
    <version>0.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>27.1-jre</version>
</dependency>
<repository>
    <snapshots>
        <enabled>false</enabled>
    </snapshots>
    <id>kotlinx</id>
    <name>bintray</name>
    <url>https://dl.bintray.com/kotlin/kotlinx</url>
</repository>

2.2. Gradle

repositories {
    maven {
        url "https://dl.bintray.com/kotlin/kotlinx"
    }
}

// https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-collections-immutable
compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-collections-immutable', version: '0.1'
// https://mvnrepository.com/artifact/com.google.guava/guava
compile group: 'com.google.guava', name: 'guava', version: '27.1-jre'

3. 不変性の種類

始める前に、コレクションが持つことができるさまざまなタイプの不変性を見てみましょう。

  1. Mutable –リストの内容は自由に変更できます
  2. 読み取り専用–コレクションの内容は変更されません。 ただし、基になるデータは変更できます
  3. Immutable –コレクションの内容を変更することはできません

不変のコレクションは、プログラミングで多くの用途があります。 たとえば、競合状態に陥るリスクなしに、異なるスレッド間で自由に共有できます。 また、不変のコレクションの実装は、それらの可変の代替案よりも常にメモリ効率が高くなります

さらに、Immutableコレクションの使用は、優れた防御プログラミング手法であり、データに不要な変更を加えないようにします。

4. Kotlinコレクション

Kotlinでは、Listなどのすべての非可変コレクションは、デフォルトでコンパイル時の読み取り専用であり、不変ではありません。 定義されたインターフェイスはコレクション内のデータを変更するメソッドをサポートしていませんが、基になるデータは変更できます。

読み取り専用のリストを変更して、これを示しましょう。

@Test
fun givenReadOnlyList_whenCastToMutableList_checkNewElementsAdded(){

    val list: List<String> = listOf("This", "Is", "Totally", "Immutable")

    (list as MutableList<String>)[2] = "Not"

    assertEquals(listOf("This", "Is", "Not", "Immutable"), list)

}

上記の例では、新しいリストを作成し、それをリスト変数に割り当てます。 デフォルトでは、Kotlinのリストインターフェイスは読み取り専用であり、リストに新しい要素を追加することはできません。 ただし、ListMutableListにキャストすることで、addメソッドを使用して新しい要素を自由に追加できます。

5. グアバ

絶対的な不変性のために、Guavaの不変コレクションのセットを利用できます。 Guavaは、 ImmutableList、ImmutableSet、ImmutableMapなどの多くのJavaコレクションの不変バージョンを提供します。

[X21X]ImmutableList.ofメソッドを使用してImmutableListの動作を見てみましょう:

@Rule
@JvmField
var ee : ExpectedException = ExpectedException.none()

@Test 
fun givenImmutableList_whenAddTried_checkExceptionThrown() { 

    val list: List<String> = ImmutableList.of("I", "am", "actually", "immutable") 
    ee.expect(UnsupportedOperationException::class.java) 
    (list as MutableList<String>).add("Oops") 

}

この例では、 MutableList にキャストしても、リストがミューテーションに抵抗することがわかります。 新しい要素を受け入れるのではなく、実行時にUnsupportedOperationExceptionがスローされます。

Guavaは、不変リストをインスタンス化する代替方法も提供します。

5.1. CopyOf

次に、以前に作成した可変リストからImmutableListをインスタンス化する方法を見てみましょう。 これを実現するために、copyOfメソッドを使用できます。このメソッドは、引数として別のコレクションを取ります。

@Rule 
@JvmField 
var ee : ExpectedException = ExpectedException.none()

@Test
fun givenMutableList_whenCopiedAndAddTried_checkExceptionThrown(){

    val mutableList : List<String> = listOf("I", "Am", "Definitely", "Immutable")

    (mutableList as MutableList<String>)[2] = "100% Not"

    assertEquals(listOf("I", "Am", "100% Not", "Immutable"), mutableList)

    val list: List<String> = ImmutableList.copyOf(mutableList)

    ee.expect(UnsupportedOperationException::class.java)

    (list as MutableList<String>)[2] = "Really?"

}

ここでは、listOfメソッドを使用して完全に変更可能なリストを作成します。 次に、 copyOf メソッドを使用して、不変のものを作成します。 最後に、要素が ImmutableList に追加されると、コードが例外をスローすることがわかります。

5.2. 便利なビルダー

最後に、Guavaが不変のコレクションを構築するために提供する便利なビルダーを見てみましょう。 この例では、 ImmutableSet。 ImmutableSet.Builderは、他のコレクションのコピーだけでなく、新しい単一要素を追加するためのすべての基本的なメソッドを提供します。

@Rule 
@JvmField 
var ee : ExpectedException = ExpectedException.none()

@Test
fun givenImmutableSetBuilder_whenAddTried_checkExceptionThrown(){

    val mutableList : List<String> = ArrayList(listOf("Hello", "Baeldung"))
    val set: ImmutableSet<String> = ImmutableSet.builder<String>()
      .add("I","am","immutable") 
      .addAll(mutableList)
      .build() 

    assertEquals(setOf("Hello", "Baeldung", "I", "am", "immutable"), set)

    ee.expect(UnsupportedOperationException::class.java) 

    (set as MutableSet<String>).add("Oops") 

}

上記の例では、Guavaの ImmutableSet.Builder クラスを使用して、StringsImmutableSetを作成します。 まず、ビルダーのaddAllメソッドを使用してmutableListのコンテンツを追加する前に、addメソッドを使用していくつかの単一文字列を追加します。

内容を検証した後、予想どおり、さらに要素を追加しようとすると、例外がスローされることがわかります。

6. Kotlinx不変コレクションライブラリ

Kotlinのコレクションの読み取り専用の性質に対するJetBrainの答えは、 Kotlinx Immutable Collections Library です。これは一口ですが、略してKICLと呼びましょう。 KICL は、Kotlinの不変のコレクションインターフェイスと実装プロトタイプを提供します。

KICL は、Guavaの2.6MBのほんの一部の重量で、アプリケーションにわずかな不変性が必要な場合に、Guavaの軽量な代替手段を提供します。

このライブラリの動作を簡単に見てみましょう。

@Rule
@JvmField
var ee : ExpectedException = ExpectedException.none()

@Test
fun givenKICLList_whenAddTried_checkExceptionThrown(){

    val list: ImmutableList<String> = immutableListOf("I", "am", "immutable")

    list.add("My new item")

    assertEquals(listOf("I", "am", "immutable"), list)
    
}

Kotlinxの不変コレクションライブラリは、Guavaのコレクションとは少し異なる動作をします。 上記のように、 UnsupportedOperationExceptionをスローするのではなく、ImmutableListに新しい要素が追加されることはありません。

7. 結論

この記事では、Kotlinが不変のコレクションに関して提供するものを見てきました。

次に、GoogleのGuavaが提供できる不変のコレクションと、Kotlinxの不変コレクションライブラリについて詳しく調べました。

すべてのコードスニペットは、GitHubにあります。