Kotlin tidak memiliki keyword static seperti Java. Sebagai gantinya, Kotlin menyediakan companion object — sebuah object yang dideklarasikan di dalam class dengan keyword companion. Anggota companion object dapat dipanggil langsung lewat nama class — seolah static member di Java — namun anggota tersebut sebenarnya adalah method pada instance object tunggal yang berada di dalam class. Pada chapter ini kita akan mendeklarasikan companion object, mengakses anggotanya via nama class, memberi nama pada companion object, dan memakainya sebagai lokasi factory method.

Apa Itu Companion Object #

Companion object adalah object yang dideklarasikan di dalam body class dengan keyword companion. Setiap class hanya boleh memiliki satu companion object. Anggota companion object dapat diakses langsung lewat nama class — tidak perlu menyebut nama companion object, walaupun nama companion object dapat juga dipakai sebagai prefiks bila companion object tersebut diberi nama.

Companion object adalah instance nyata, bukan kumpulan method statis. Karena instance, companion object dapat mengimplementasikan interface, memiliki state, dan diteruskan sebagai parameter function. Kombinasi singleton-instance dan akses via nama class memberi Kotlin fleksibilitas yang tidak dimiliki static class Java.

Deklarasi Sederhana #

Contoh berikut mendefinisikan class Lingkaran dengan companion object yang menyimpan konstanta PI dan NAMA serta dua factory method buatSatuan() dan dariDiameter(). Class Warna menunjukkan companion object dengan nama eksplisit Pabrik. Class Suhu memakai companion object untuk menyimpan konstanta titik beku/didih dan factory dengan konversi satuan.

kotlin
// companion_objects.kt
// Mendemonstrasikan `companion object` di dalam class.

class Lingkaran(private val jariJari: Double) {
    // Companion object menyediakan anggota statis untuk class Lingkaran.
    companion object {
        const val PI = 3.14159
        const val NAMA = "Lingkaran"

        // Factory method: membuat instance dari nilai default.
        fun buatSatuan(): Lingkaran = Lingkaran(1.0)

        // Factory method: membuat instance dari diameter.
        fun dariDiameter(diameter: Double): Lingkaran = Lingkaran(diameter / 2.0)
    }

    fun luas(): Double = PI * jariJari * jariJari
    fun keliling(): Double = 2 * PI * jariJari

    override fun toString(): String = "$NAMA(jariJari=$jariJari)"
}

class Warna {
    // Companion object bernama — dapat dipanggil via `Warna.Pabrik.buat(...)`.
    companion object Pabrik {
        private val kunciNilai = mapOf(
            "merah" to "#FF0000",
            "hijau" to "#00FF00",
            "biru" to "#0000FF",
        )

        fun buat(nama: String): Warna = Warna(kunciNilai[nama.lowercase()] ?: "#000000")
    }

    val kode: String

    private constructor(kode: String) {
        this.kode = kode
    }
}

class Suhu(private val celsius: Double) {
    companion object {
        const val TITIK_BEKU = 0.0
        const val TITIK_DIDIH = 100.0

        // Factory methods dengan konversi satuan.
        fun dariFahrenheit(f: Double): Suhu = Suhu((f - 32) * 5 / 9)
        fun dariKelvin(k: Double): Suhu = Suhu(k - 273.15)
    }

    fun celsius(): Double = celsius
    fun fahrenheit(): Double = celsius * 9 / 5 + 32

    fun status(): String = when {
        celsius <= TITIK_BEKU -> "beku"
        celsius >= TITIK_DIDIH -> "didih"
        else -> "cair"
    }
}

fun main() {
    // Akses konstanta via companion object — seolah static member Java.
    println("PI = ${Lingkaran.PI}")
    println("NAMA = ${Lingkaran.NAMA}")
    println()

    val l1 = Lingkaran.buatSatuan()
    val l2 = Lingkaran.dariDiameter(10.0)
    println(l1)
    println(l2)
    println("luas l2 = ${l2.luas()}")
    println("keliling l2 = ${l2.keliling()}")
    println()

    // Companion object bernama: akses via nama `Pabrik`.
    val merah = Warna.Pabrik.buat("Merah")
    val hijau = Warna.Pabrik.buat("Hijau")
    val tidakDikenal = Warna.Pabrik.buat("Magenta")
    println("Warna merah: ${merah.kode}")
    println("Warna hijau: ${hijau.kode}")
    println("Warna 'Magenta' (tidak ada): ${tidakDikenal.kode}")
    println()

    val s1 = Suhu.dariFahrenheit(32.0)
    val s2 = Suhu.dariKelvin(373.15)
    println("32 F = ${s1.celsius()} C — status: ${s1.status()}")
    println("373.15 K = ${s2.celsius()} C — status: ${s2.status()}")
    println("100 C dalam F = ${Suhu(100.0).fahrenheit()}")
}

Output yang Dihasilkan:

text
PI = 3.14159
NAMA = Lingkaran

Lingkaran(jariJari=1.0)
Lingkaran(jariJari=5.0)
luas l2 = 78.53975
keliling l2 = 31.4159

Warna merah: #FF0000
Warna hijau: #00FF00
Warna 'Magenta' (tidak ada): #000000

32 F = 0.0 C — status: beku
373.15 K = 100.0 C — status: didih
100 C dalam F = 212.0

Mari kita bedah kode di atas:

  1. companion object { const val PI = 3.14159; const val NAMA = "Lingkaran"; ... }: companion object tanpa nama di dalam class Lingkaran. Anggotanya dapat diakses langsung via Lingkaran.PI, Lingkaran.NAMA, dan Lingkaran.buatSatuan() — seolah static member di Java.
  2. const val PI = 3.14159: const val hanya boleh dipakai di top-level atau di dalam object (termasuk companion object). Nilai literal diinline oleh kompilator, sehingga tidak ada overhead akses property saat runtime.
  3. fun buatSatuan(): Lingkaran = Lingkaran(1.0): factory method di companion object. Memanggil Lingkaran.buatSatuan() menghasilkan instance Lingkaran dengan jari-jari 1.0 — alternatif terhadap memanggil Lingkaran(1.0) langsung, yang dapat membuat kode pemanggil lebih jelas tentang maksud.
  4. fun dariDiameter(diameter: Double): Lingkaran: factory method dengan konversi — pemanggil memberi diameter, method menghitung jari-jari dan membuat instance. Pola ini menyembunyikan detail konversi dari pemanggil.
  5. override fun toString(): String = "$NAMA(jariJari=$jariJari)": method instance dapat membaca konstanta di companion object tanpa prefiks — kompilator otomatis merujuk ke Companion.NAMA.
  6. companion object Pabrik { ... } di class Warna: companion object diberi nama Pabrik. Anggotanya dapat dipanggil baik via Warna.buat(...) maupun via Warna.Pabrik.buat(...). Memberi nama berguna saat kita ingin merujuk ke companion object sebagai instance (misalnya meneruskannya ke function yang menerima tipe tertentu).
  7. private constructor(kode: String) di class Warna: constructor utama dibuat private, sehingga instance Warna hanya dapat dibuat melalui factory method Pabrik.buat(...). Pola ini memberi kontrol penuh pada pembuatan instance — misalnya memakai cache, memvalidasi, atau memetakan input ke instance yang sudah ada.
  8. Suhu.dariFahrenheit(32.0) dan Suhu.dariKelvin(373.15): beberapa factory method untuk membuat instance dari satuan yang berbeda. Pemanggil memilih factory sesuai sumber data — dariFahrenheit untuk input Fahrenheit, dariKelvin untuk input Kelvin — dan tetap mendapatkan instance Suhu yang konsisten.
  9. Suhu.TITIK_BEKU dan Suhu.TITIK_DIDIH: konstanta bersifat publik yang dapat dipakai kode luar untuk validasi atau perbandingan. Akses langsung via nama class membuat konstanta tampak sebagai bagian dari API class.

Companion Object Bernama vs Anonim #

Companion object dapat ditulis dengan atau tanpa nama. Companion object tanpa nama (companion object { ... }) lebih ringkas dan biasa dipakai ketika anggotanya hanya diakses via nama class. Companion object bernama (companion object Pabrik { ... }) berguna ketika kita ingin:

  • Merujuk ke companion object sebagai instance — misalnya meneruskannya sebagai argumen ke function yang menerima tipe Pabrik.
  • Memiliki beberapa “namespace” logis dalam satu class.
  • Meningkatkan keterbacaan ketika companion object berisi banyak anggota.

Factory Method Pattern #

Companion object adalah lokasi natural untuk factory method. Daripada memaksa pemanggil memanggil constructor langsung (yang mungkin memerlukan parameter yang rumit), kita menyediakan beberapa factory method dengan nama deskriptif — buatSatuan(), dariDiameter(...), dariFahrenheit(...) — sehingga kode pemanggil lebih jelas tentang maksud dan dapat memilih factory yang sesuai dengan sumber data. Constructor utama bahkan dapat dibuat private agar instance hanya dapat dibuat melalui factory, memberi kontrol penuh pada class terhadap cara instance dibuat.

Referensi Lanjutan #

Ringkasan #

Companion object menyediakan anggota yang dapat diakses via nama class — pengganti keyword static di Java. Companion object adalah instance nyata yang dapat memiliki state, mengimplementasikan interface, dan menjadi lokasi factory method. Memberi nama pada companion object memungkinkan kita merujuknya sebagai instance terpisah. Pada chapter Anonymous Objects berikutnya, kita akan melihat cara membuat instance object tanpa nama untuk implementasi ad-hoc.