【Kotlin】Objectの使い方とCompanion objects の特徴について

# はじめに
androidアプリを自作する中でKotlinについて学んだことを備忘として記事にします。
本記事では`Companion objects`の特徴について記載します。

# Object について
`Companion objects`を説明する前に、まずは`Object`の概念について説明します。
Kotlinでは`Object`を使うことでクラスを定義し、さらにそのインスタンスを1ステップで作成できます。
これは、再利用可能なシングルトンインスタンスや1回限りのオブジェクトが必要な場合に便利です。
### 特徴
- クラス定義からインスタンス定義までを1ステップで作成
- 生成されるインスタンスはシングルトンインスタンスとなる

[実行例: Kotlin Playground](https://pl.kotl.in/IwEa8No6e)

```kotlin
// データプロバイダーを管理するためのシングルトンオブジェクトを宣言
object DataProviderManager {
    // プライベートな変数
    private val providers = mutableListOf<String>()

    // プロバイダーを登録
    fun registerDataProvider(provider: String) {
        providers.add(provider)
    }

    // 登録されたプロバイダーを取得
    val allDataProviders: Collection<String>
        get() = providers
}

// 使用例
fun main() {
    DataProviderManager.registerDataProvider("provider")
    print(DataProviderManager.allDataProviders)
}

// 実行結果
// [provider]
```

# クラス内のObjectについて
クラス内でも`Object`が定義できます。
`Object`の内部変数はクラス内でも使用できます。
ただし、クラスのメンバー変数は`Object`内では使用できません。
（`Object`はクラスインスタンス生成前に利用できますが、クラスのメンバー変数は生成後にしか利用できないためです）

[実行例: Kotlin Playground](https://pl.kotl.in/IWCqSViwE)
```kotlin
class Food {
    
    fun cooking() {
        // Object内の変数はクラス内でアクセスできる
        println("cooking: ${BeefObject.GRAM}")
    }

    // シングルトンインスタンスとして生成される
    object BeefObject {
        const val GRAM = 200L
        private const val BEEF = "beef"

        fun getBeef(): String {
            return BEEF
        }
    }
}

// 使用例
fun main() {
    val food = Food()
    food.cooking()
    println(Food.BeefObject.GRAM)      // インスタンス生成済みのため、
    println(Food.BeefObject.getBeef()) // クラスをnewすることなく利用できる
}

// 実行結果
// cooking: 200
// 200
// beef
```

# Companion objectsについて
クラス内で定義する`Object`と基本的には同じですが、`Companion objects`は1クラス1つだけしか定義できません。
用途として、KotlinにはJavaのstaticの概念が存在しないため、その代替として`Companion objects`という同伴オブジェクトを使用して静的メンバーを定義することができます。

### 特徴
- クラス内のObject定義と機能的には変わらない
- 1クラスに1つの`Companion objects`しか定義できない制約がある
- Javaのstatic変数のような静的な定数などを定義するのに利用できる


[実行例: Kotlin Playground](https://pl.kotl.in/BPqTiJp5q)
```kotlin
class Food {
    
    fun cooking() {
        // Object内の変数に直接アクセスできる
        println("cooking: $GRAM")
    }

    // シングルトンインスタンスとして生成される
    companion object {
        const val GRAM = 300L
        private const val PORK = "pork"

        fun getPork(): String {
            return PORK
        }
    }
}

// 使用例
fun main() {
    val food = Food()
    food.cooking()
    println("With companion    : ${Food.Companion.GRAM}")
    println("With companion    : ${Food.Companion.getPork()}")
    println("Without companion : ${Food.GRAM}")      // Companion無しでも呼べる
    println("Without companion : ${Food.getPork()}") // Companion無しでも呼べる
}

// 実行結果
// cooking: 300
// With companion    : 300
// With companion    : pork
// Without companion : 300
// Without companion : pork
```

# Object と Companion objects の違い
機能的には違いはなさそうですが、シングルトンオブジェクトが生成されるタイミングが異なります。

|宣言|オブジェクト生成タイミング|
| ---- | ---- |
|Companion objects|クラスロードされたときに生成される|
|クラス内Object宣言|自分自身が参照されたときに生成される|

[実行例: Kotlin Playground](https://pl.kotl.in/wQ5Cg7BDs)
```kotlin
class Food {
    companion object Beef {
        init { println("初期化 = Beef") }
        fun hello() = println("Beef!!")
    }

    object Pork {
        init { println("初期化 = Pork") }
        fun hello() = println("Pork!!")
    }
}

fun main() {
    println("------------- Food クラスのインスタンス化前")
    val obj = Food()
    println("------------- Food クラスのインスタンス化後")
    println("------------- Beef Companion objects の hello() 呼び出し")
    Food.Beef.hello()
    println("------------- Pork Object の hello() 呼び出し")
    Food.Pork.hello()
}

// 実行結果
// ------------- Food クラスのインスタンス化前
// 初期化 = Beef
// ------------- Food クラスのインスタンス化後
// ------------- Beef Companion objects の hello() 呼び出し
// Beef!!
// ------------- Pork Object の hello() 呼び出し
// 初期化 = Pork
// Pork!!
```

`Companion objects`の`Beef`インスタンスは、外側のクラスの`Food`がインスタンス化されるときに生成されています。
一方で、オブジェクト宣言された`Pork`オブジェクトは`Food.Pork.hello()`関数が呼び出された時点で初めて生成されます。

# まとめ
単純にシングルトンインスタンスを生成したい場合はクラス内ではなく外側に`Object`で定義しても良いかと思います。
`Companion objects`はJavaのstaticの代替として利用する利用法がよさそうです。