覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Kotolin - コンパニオンオブジェクト 2

Kotlin のコンパニオンオブジェクトは、クラス内に宣言された通常のオブジェクトであり、
名前を付けたり、インターフェースを実装したり、拡張関数やプロパティを持つこともできます。


コンパニオンオブジェクトに名前を付ける

前回のエントリですでにやってやってますが、コンパニオンオブジェクトに名前を付けることが可能です。

import com.google.gson.Gson

class Person(val name: String, val age: Int) {
    // 名前付きのコンパニオンオブジェクトの宣言
    companion object Decoder {
        fun fromJSON(jsonStr: String): Person {
            return Gson().fromJson(jsonStr, Person::class.java)
        }
    }
}

呼び出し側のコードは以下の通りで、コンパニオンオブジェクトの名前を明示することも省略することも可能です。

// コンパニオンオブジェクトの名前を指定して関数を呼び出せる
val p1 = Person.Decoder.fromJSON("""{"name": "Kotlin Taro", "age": 32}""")
println("${p1.name} is ${p1.age} years old.")  // -> Kotlin Taro is 32 years old.

// コンパニオンオブジェクトの名前を省略することもできる
val p2 = Person.fromJSON("""{"name": "Java Taro", "age": 39}""")
println("${p2.name} is ${p2.age} years old.")  // -> Java Taro is 39 years old.


コンパニオンオブジェクトでインターフェースを実装する

他のオブジェクト宣言と同じくコンパニオンオブジェクトはインターフェースを実装できます。
インターフェースを実装しているオブジェクトのインスタンスとして、そのオブジェクトを含むクラスの名前を使用できます。

JSONFactory というインターフェースを介してオブジェクトが生成されるものとする場合、
以下のようなサンプルコードを書くことができます。

import com.google.gson.Gson

interface JSONFactory<T> {
    fun fromJSON(jsonStr: String): T
}

class Person(val name: String, val age: Int) {
    // 名前付きのコンパニオンオブジェクトの宣言
    companion object : JSONFactory<Person> {
        override fun fromJSON(jsonStr: String): Person {
            return Gson().fromJson(jsonStr, Person::class.java)
        }
    }
}


JSONFactoryを引数に取る関数を定義し多とした場合、
その関数を呼び出す場合には、JSONFactoryをコンパニオンオブジェクトが実装しているので Person オブジェクトを引数に渡すことが可能です。

fun <T> loadFromJSON(jsonStr: String, factory: JSONFactory<T>): T {
    return factory.fromJSON(jsonStr)
}

fun main() {
    // JSONFactoryインターフェースの
    val p1 = loadFromJSON("""{"name": "Java Taro", "age": 45}""", Person) // コンパニオンオブジェクトのインスタンスを関数に渡す
    println("${p1.name} is ${p1.age} years old.")
}


コンパニオンオブジェクトの拡張関数

クラスがコンパニオンオブジェクトを持っていれば、コンパニオンオブジェクトに対して拡張関数を定義することが実現できます。
例えば ClazzA がコンパニオンオブジェクトを持つ時、ClazzA.Companion に対する拡張関数 function を定義したと仮定すると、
その関数を ClazzA.function() として呼び出すことができます。


すでに定義した Person クラスは、そのドメインに特化したビジネスロジックのみを持つものとしておきたい場合に、
データのシリアライズやデシリアライズのルールは分離しておきたい(Personの核となるドメインとは別にしておきたい)ことがあります。
このような場合に拡張関数を利用すると実現可能です。

import com.google.gson.Gson

// domain core module
class Person(val name: String, val age: Int) {
    // 空のコンパニオンオブジェクトを宣言しておく
    companion object {
    }
}

// client / server communication module
fun Person.Companion.fromJSON(jsonStr: String): Person {
    return Gson().fromJson(jsonStr, Person::class.java)
}

以下のように実行可能です。

val p1 = Person.fromJSON("""{"name": "Java Taro", "age": 45}""")
println("${p1.name} is ${p1.age} years old.")


まとめ

今回は、Kotlin のコンパニオンオブジェクトへの名前付け、インターフェースの実装、拡張関数の定義が可能であることを確認しました。



関連エントリ