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.
// 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:
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.0Mari kita bedah kode di atas:
companion object { const val PI = 3.14159; const val NAMA = "Lingkaran"; ... }: companion object tanpa nama di dalam classLingkaran. Anggotanya dapat diakses langsung viaLingkaran.PI,Lingkaran.NAMA, danLingkaran.buatSatuan()— seolah static member di Java.const val PI = 3.14159:const valhanya boleh dipakai di top-level atau di dalamobject(termasuk companion object). Nilai literal diinline oleh kompilator, sehingga tidak ada overhead akses property saat runtime.fun buatSatuan(): Lingkaran = Lingkaran(1.0): factory method di companion object. MemanggilLingkaran.buatSatuan()menghasilkan instanceLingkarandengan jari-jari 1.0 — alternatif terhadap memanggilLingkaran(1.0)langsung, yang dapat membuat kode pemanggil lebih jelas tentang maksud.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.override fun toString(): String = "$NAMA(jariJari=$jariJari)": method instance dapat membaca konstanta di companion object tanpa prefiks — kompilator otomatis merujuk keCompanion.NAMA.companion object Pabrik { ... }di classWarna: companion object diberi namaPabrik. Anggotanya dapat dipanggil baik viaWarna.buat(...)maupun viaWarna.Pabrik.buat(...). Memberi nama berguna saat kita ingin merujuk ke companion object sebagai instance (misalnya meneruskannya ke function yang menerima tipe tertentu).private constructor(kode: String)di classWarna: constructor utama dibuatprivate, sehingga instanceWarnahanya dapat dibuat melalui factory methodPabrik.buat(...). Pola ini memberi kontrol penuh pada pembuatan instance — misalnya memakai cache, memvalidasi, atau memetakan input ke instance yang sudah ada.Suhu.dariFahrenheit(32.0)danSuhu.dariKelvin(373.15): beberapa factory method untuk membuat instance dari satuan yang berbeda. Pemanggil memilih factory sesuai sumber data —dariFahrenheituntuk input Fahrenheit,dariKelvinuntuk input Kelvin — dan tetap mendapatkan instanceSuhuyang konsisten.Suhu.TITIK_BEKUdanSuhu.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 #
- Object declarations — bagian companion object untuk anggota static per class
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.