Menu
Coddy logo textTech

コンパニオンオブジェクト

CoddyのKotlinジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 13/57。

一部のメンバーは、1つのインスタンスではなく、クラス全体に属します。Kotlinでは、クラス内のcompanion objectにそれらを配置し、クラス名を通して呼び出します。

class Temperature(val celsius: Double) {
    companion object {
        fun fromFahrenheit(f: Double) = Temperature((f - 32) * 5 / 9)
    }
}

mainの中では:

val boiling = Temperature.fromFahrenheit(212.0)
println(boiling.celsius)

出力:

100.0

fromFahrenheit はファクトリ関数です。つまり、インスタンスを作成する関数です。コンストラクターとは異なり、説明的な名前を持ち、オブジェクトの代わりに null を返すこともできます。private constructor を使うと、ファクトリだけが生成への入口となるため、最初に入力を確認して整理できます。

class Username private constructor(val text: String) {
    companion object {
        fun of(raw: String): Username? {
            val clean = raw.trim().lowercase()
            return if (clean.length in 3..12) Username(clean) else null
        }
    }
}

main の中では次のようにします。

println(Username.of("  Ada_L ")?.text)
println(Username.of("x")?.text)
// Username("x")   // エラー: コンストラクタは private です

出力:

ada_l
null

コンパニオンオブジェクトはクラスに属しているため、privateコンストラクターを呼び出すことができ、クラス自身のコードでは接頭辞なしでコンパニオンのメンバーを使用できます。ここでは、すべてのチケットが、全チケットで共有される1つのカウンターから次の番号を取得します。

class Ticket(val holder: String) {
    val number = nextNumber++

    companion object {
        private var nextNumber = 1
        val issued: Int
            get() = nextNumber - 1
    }
}

mainの中では:

val a = Ticket("Ada")
val b = Ticket("Bo")
println("${a.holder} #${a.number}, ${b.holder} #${b.number}")
println("issued: ${Ticket.issued}")

出力:

Ada #1, Bo #2
issued: 2

Kotlinにはstaticキーワードがありません。コンパニオンのメンバーがその役割を果たします。クラス全体で1つのコピーがあり、ClassName.memberとしてアクセスします。通常のオブジェクトと同様に、コンパニオンはクラスが初めて使用されたときに1度だけ作成されます。ファクトリや共有カウンターなど、1つのインスタンスに依存しないものをここに置きます。インスタンスのデータを読み取るものはすべて、通常のメンバーのままにします。

class User private constructor(val name: String) {
    fun greet() = "hi $name"             // インスタンスが必要: user.greet()

    companion object {
        fun guest() = User("guest")      // クラスに属する: User.guest()
    }
}
challenge icon

チャレンジ

簡単

Coupon クラスを完成させてください。コンストラクターは private なので、クーポンはコンパニオンオブジェクト内のファクトリー Coupon.create(code, percent) で作成します。コードが大文字の英字と数字で構成された 4~10 文字で、percent が 1~90 の範囲内でない限り、null を返します。コンパニオンオブジェクトは、作成したクーポンの数も created に記録します。この値は外部のコードから読み取れますが、変更することはできません。apply(price) は割引分を差し引いた価格を切り捨てて返します(price * (100 - percent) / 100)。

用意されているコードは、行 SAVE10 10 200(コード、percent、価格)を読み取ります。SAVE10: 180 または invalid coupon SAVE10 を出力し、最後に件数を付けて created: を出力します。

コードは Coupon.kt に記述してください。Main.kt には用意された入出力コードが含まれており、編集できません。

自分で試してみよう

fun main() {
    // 提供された入出力コード: そのままにしておく
    val input = generateSequence(::readLine).toList()
    for (line in input) {
        val (code, percent, price) = line.split(" ")
        val coupon = Coupon.create(code, percent.toInt())
        if (coupon == null) println("invalid coupon $code") else println("${coupon.code}: ${coupon.apply(price.toInt())}")
    }
    println("created: ${Coupon.created}")
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Kotlinプレイグラウンド