Sebuah singleton adalah class yang hanya boleh memiliki satu instance selama aplikasi berjalan. Pola ini sering dipakai untuk konfigurasi global, pabrik object, registry, dan koneksi terpusat. Di Java, singleton biasanya ditulis dengan kombinasi private constructor, field static INSTANCE, dan method getInstance() yang mengurus inisialisasi thread-safe. Kotlin menyederhanakan pola tersebut menjadi satu keyword object. Pada chapter ini kita akan mendeklarasikan singleton dengan object, melihat inisialisasi lazy yang dilakukan otomatis, dan memakai object sebagai factory yang menghasilkan instance class lain.

Apa Itu object Declaration #

Declaration object Nama { ... } di Kotlin mendefinisikan sekaligus membuat instance tunggal dari sebuah class anonim. Kompilator menjamin hanya ada satu instance selama aplikasi berjalan, dan instance tersebut dibuat secara lazy — instance hanya diinisialisasi ketika pertama kali diakses. Setelah dibuat, instance tersebut di-share ke seluruh kode yang menyebut nama object tersebut.

Akses ke anggota object langsung lewat nama object — tidak perlu membuat instance dengan Nama() karena instance sudah otomatis ada. Penulisan mirip dengan memanggil static member di Java, namun di Kotlin yang dipanggil adalah method pada instance singleton, bukan method statis.

Singleton Sederhana #

Contoh berikut mendefinisikan tiga object: Konfigurasi untuk konstanta aplikasi, PabrikPengguna sebagai factory yang menghasilkan instance Pengguna, dan PenyanggaLog dengan init block yang berjalan sekali saat singleton pertama kali diakses.

kotlin
// object_declarations.kt
// Mendemonstrasikan `object` declaration sebagai singleton di Kotlin.

object Konfigurasi {
    const val NAMA_APLIKASI = "BelajarKode"
    const val VERSI = "1.0"

    // State singleton — di-share ke seluruh aplikasi.
    var jumlahPermintaan = 0
        private set

    fun catatPermintaan() {
        jumlahPermintaan++
        println("Permintaan #$jumlahPermintaan diproses oleh $NAMA_APLIKASI v$VERSI")
    }

    fun ringkasan(): String = "$NAMA_APLIKASI v$VERSI — $jumlahPermintaan permintaan"
}

object PabrikPengguna {
    private var urutan = 1000

    fun buat(nama: String): Pengguna {
        urutan++
        return Pengguna(urutan, nama)
    }

    fun daftarDefault(): List<Pengguna> = listOf(
        buat("Budi"),
        buat("Siti"),
        buat("Dewi"),
    )
}

data class Pengguna(val id: Int, val nama: String)

// Singleton dengan init block — kode dijalankan sekali saat akses pertama.
object PenyanggaLog {
    private val baris = mutableListOf<String>()

    init {
        // Inisialisasi sekali saat instance singleton dibuat.
        baris.add("[boot] singleton PenyanggaLog siap")
    }

    fun catat(pesan: String) {
        baris.add(pesan)
    }

    fun ambilSemua(): List<String> = baris.toList()
}

fun main() {
    // Akses singleton langsung lewat nama `object`.
    println(Konfigurasi.NAMA_APLIKASI)
    println(Konfigurasi.VERSI)
    println()

    Konfigurasi.catatPermintaan()
    Konfigurasi.catatPermintaan()
    Konfigurasi.catatPermintaan()
    println(Konfigurasi.ringkasan())
    println()

    val p1 = PabrikPengguna.buat("Andi")
    val p2 = PabrikPengguna.buat("Maya")
    println("Pengguna baru: ${p1.nama} (id=${p1.id})")
    println("Pengguna baru: ${p2.nama} (id=${p2.id})")
    println("Daftar default: ${PabrikPengguna.daftarDefault().map { it.nama }}")
    println()

    PenyanggaLog.catat("permintaan diproses")
    PenyanggaLog.catat("keluar aplikasi")
    println("Isi PenyanggaLog:")
    for (baris in PenyanggaLog.ambilSemua()) {
        println("  - $baris")
    }
}

Output yang Dihasilkan:

text
BelajarKode
1.0

Permintaan #1 diproses oleh BelajarKode v1.0
Permintaan #2 diproses oleh BelajarKode v1.0
Permintaan #3 diproses oleh BelajarKode v1.0
BelajarKode v1.0 — 3 permintaan

Pengguna baru: Andi (id=1001)
Pengguna baru: Maya (id=1002)
Daftar default: [Budi, Siti, Dewi]

Isi PenyanggaLog:
  - [boot] singleton PenyanggaLog siap
  - permintaan diproses
  - keluar aplikasi

Mari kita bedah kode di atas:

  1. object Konfigurasi { const val NAMA_APLIKASI = ...; var jumlahPermintaan = 0 }: deklarasi object sekaligus membuat instance tunggal. Konstanta NAMA_APLIKASI dan VERSI memakai const val karena nilainya literal yang dapat diinline saat kompilasi.
  2. var jumlahPermintaan = 0; private set: state singleton yang dapat dibaca dari luar namun hanya dapat diubah dari dalam. Setiap pemanggilan catatPermintaan() menambah nilai ini — karena instance tunggal, perubahan terlihat oleh semua kode yang mengakses Konfigurasi.jumlahPermintaan.
  3. Konfigurasi.catatPermintaan(): akses method singleton langsung lewat nama object. Tidak ada sintaks Konfigurasi.getInstance() seperti pola Java klasik.
  4. object PabrikPengguna { private var urutan = 1000; fun buat(...): Pengguna }: object sebagai factory yang menjaga state urutan ID. Setiap pemanggilan buat() menambah urutan dan mengembalikan instance Pengguna baru dengan ID berurutan.
  5. Pengguna(id=1001), Pengguna(id=1002): bukti state urutan dipertahankan antar pemanggilan — instance PabrikPengguna tetap sama, sehingga counter tidak di-reset.
  6. init { baris.add("[boot] ...") } di dalam object: init block pada object dijalankan sekali saat instance singleton pertama kali dibuat (lazy). Karena output menampilkan baris [boot] lebih dulu daripada dua baris berikutnya, terlihat bahwa init berjalan sebelum method catat dipanggil.
  7. PenyanggaLog.ambilSemua(): mengembalikan salinan list sebagai List<String> — pola defensif agar pemanggil tidak dapat memodifikasi list internal singleton secara langsung.

Inisialisasi Lazy #

Kompilator Kotlin menghasilkan kode yang menjamin inisialisasi singleton bersifat thread-safe dan lazy. Instance object hanya dibuat saat pertama kali diakses, bukan saat aplikasi dimulai. Untuk PenyanggaLog, instance baru dibuat ketika PenyanggaLog.catat(...) pertama dipanggil — init block berjalan tepat saat itu juga, sebelum method catat dieksekusi.

Sifat lazy berguna ketika inisialisasi singleton mahal (misalnya membaca file konfigurasi atau membuka koneksi database). Singleton yang tidak pernah diakses tidak akan mengonsumsi resource.

object sebagai Factory dan Registry #

Pemakaian umum object selain konfigurasi global adalah factory dan registry. PabrikPengguna di atas adalah contoh factory: pemanggil tidak peduli bagaimana instance Pengguna dibuat, mereka hanya memanggil PabrikPengguna.buat(nama) dan menerima instance siap pakai dengan ID yang dijamin unik oleh state internal factory. Bila kita mengubah algoritma pembuatan ID (misalnya menambahkan prefix), hanya satu tempat yang perlu diubah.

Referensi Lanjutan #

Ringkasan #

Deklarasi object membuat singleton secara langsung dengan instance tunggal dan inisialisasi lazy yang thread-safe. Akses anggota object langsung lewat nama tanpa membuat instance. object cocok untuk konfigurasi global, factory, dan registry yang membutuhkan satu instance bersama. Pada chapter companion Objects berikutnya, kita akan melihat bagaimana object dapat diletakkan di dalam class untuk menyediakan anggota statis.