Kadang kita membutuhkan instance yang mengimplementasikan interface atau mewarisi class, namun instance tersebut hanya dipakai di satu tempat dan tidak perlu diberi nama class. Java menyelesaikan kebutuhan ini dengan anonymous class — sintaks new Type() { ... }. Kotlin memiliki padanannya: anonymous object dengan sintaks object : Type { ... }. Anonymous object membuat instance sekaligus mendefinisikan body class anonim tersebut. Pada chapter ini kita akan membuat anonymous object yang mengimplementasikan interface, mengimplementasikan beberapa interface sekaligus, dan memakai anonymous object sebagai callback listener.

Apa Itu Anonymous Object #

Anonymous object adalah instance dari class anonim yang dibuat dengan ekspresi object : Supertype { ... }. Bagian setelah : menentukan supertype (interface atau class open), lalu body di dalam { } mengimplementasikan method abstrak dan dapat menambah method baru. Berbeda dari deklarasi object Nama { ... } yang membuat singleton dengan nama, anonymous object tidak memiliki nama class — instance-nya langsung disimpan ke variabel atau diteruskan ke function.

Anonymous object cocok untuk implementasi ad-hoc: ketika kita perlu instance yang mengimplementasikan sebuah interface, namun instance tersebut hanya dipakai sekali dan tidak perlu dipakai ulang di tempat lain. Sintaks object : ... lebih ringkas daripada mendeklarasikan class baru bernama untuk implementasi yang spesifik.

Implementasi Interface #

Contoh berikut menunjukkan anonymous object sebagai KlikListener untuk class Tombol. Listener tersebut mengimplementasikan dua method interface dan menangkap variabel tombol dari scope sekitarnya. Contoh juga mendemonstrasikan anonymous object yang mengimplementasikan dua interface sekaligus.

kotlin
// anonymous_objects.kt
// Mendemonstrasikan anonymous object dengan `object : Type { ... }`.

interface KlikListener {
    fun padaKlik(posisi: Int): Unit
    fun padaKlikGanda(posisi: Int): Unit
}

class Tombol(private val label: String) {
    private var listener: KlikListener? = null

    fun aturListener(l: KlikListener) {
        listener = l
    }

    fun klik(posisi: Int) {
        listener?.padaKlik(posisi)
    }

    fun klikGanda(posisi: Int) {
        listener?.padaKlikGanda(posisi)
    }

    fun deskripsi(): String = "Tombol '$label'"
}

// Anonymous object yang mengimplementasikan multiple interface.
interface Berhitung {
    fun tambah(a: Int, b: Int): Int
}

interface Berlog {
    fun cetak(pesan: String)
}

fun buatKalkulator(): Berhitung {
    // Anonymous object: tidak ada nama class, langsung dibuat instance.
    return object : Berhitung {
        override fun tambah(a: Int, b: Int): Int = a + b
    }
}

fun main() {
    val tombol = Tombol("Simpan")
    tombol.aturListener(object : KlikListener {
        override fun padaKlik(posisi: Int) {
            println("[${tombol.deskripsi()}] diklik pada posisi $posisi")
        }

        override fun padaKlikGanda(posisi: Int) {
            println("[${tombol.deskripsi()}] diklik ganda pada posisi $posisi")
        }
    })

    tombol.klik(1)
    tombol.klikGanda(2)
    println()

    // Anonymous object yang mengimplementasikan dua interface sekaligus.
    val kalkulatorCetak = object : Berhitung, Berlog {
        override fun tambah(a: Int, b: Int): Int = a + b
        override fun cetak(pesan: String) = println("LOG: $pesan")
    }
    println("5 + 3 = ${kalkulatorCetak.tambah(5, 3)}")
    kalkulatorCetak.cetak("operasi tambah selesai")
    println()

    val k = buatKalkulator()
    println("10 + 4 = ${k.tambah(10, 4)}")
    println("Tipe k: ${k::class.simpleName}")
}

Output yang Dihasilkan:

text
[Tombol 'Simpan'] diklik pada posisi 1
[Tombol 'Simpan'] diklik ganda pada posisi 2

5 + 3 = 8
LOG: operasi tambah selesai

10 + 4 = 14
Tipe k: null

Mari kita bedah kode di atas:

  1. interface KlikListener { fun padaKlik(posisi: Int); fun padaKlikGanda(posisi: Int) }: kontrak untuk listener klik. Dua method tanpa body — class yang mengimplementasikan wajib menyediakan body keduanya.
  2. tombol.aturListener(object : KlikListener { override fun padaKlik(...) { ... }; override fun padaKlikGanda(...) { ... } }): anonymous object dibuat langsung sebagai argumen function. Body mengimplementasikan kedua method interface. Tidak ada nama class — instance dibuat sekali untuk pemakaian ini.
  3. println("[${tombol.deskripsi()}] diklik pada posisi $posisi"): body method anonymous object dapat menangkap variabel tombol dari scope sekitarnya. Karena anonymous object adalah closure, ia dapat membaca variabel yang dideklarasikan di luar body.
  4. object : Berhitung, Berlog { ... }: anonymous object dapat mengimplementasikan beberapa interface sekaligus. Method tambah berasal dari Berhitung, method cetak berasal dari Berlog. Satu instance memenuhi kedua kontrak.
  5. fun buatKalkulator(): Berhitung { return object : Berhitung { override fun tambah(...) } }: function yang mengembalikan anonymous object. Tipe kembalian function adalah interface Berhitung, sehingga pemanggil hanya melihat method yang dideklarasikan interface — method lain (jika ada) tidak dapat dipanggil dari luar.
  6. k::class.simpleName: mengambil nama class instance k. Output menunjukkan null karena anonymous object tidak punya nama class sederhana — class anonim di JVM biasanya diberi nama berisi karakter $ yang tidak dianggap nama sederhana.

Anonymous Object sebagai Callback #

Pemakaian anonymous object paling umum adalah sebagai callback — instance yang diteruskan ke API lain dan dipanggil kembali ketika event terjadi. Pada contoh Tombol, anonymous object KlikListener adalah callback: class Tombol menyimpan referensi listener dan memanggil methodnya ketika klik() atau klikGanda() dipanggil. Pola ini mengizinkan kode pemanggil untuk menentukan apa yang harus dilakukan saat event terjadi, tanpa mengubah kode class Tombol.

Anonymous object cocok untuk callback karena:

  • Implementasi spesifik untuk event tertentu dapat didefinisikan langsung di tempat pendaftaran listener, membuat kode lebih mudah dibaca.
  • Body anonymous object dapat menangkap variabel scope sekitar (seperti tombol), sehingga handler dapat berinteraksi dengan state pemanggil.
  • Tidak perlu membuat class baru bernama untuk setiap kemungkinan handler.

Multiple Interface Implementation #

Anonymous object dapat mengimplementasikan beberapa interface sekaligus dengan sintaks object : A, B { ... }. Pada contoh di atas, kalkulatorCetak mengimplementasikan Berhitung dan Berlog, sehingga instance dapat memanggil tambah() (dari Berhitung) dan cetak() (dari Berlog). Pola ini berguna ketika sebuah instance perlu memenuhi beberapa kontrak sekaligus namun tidak pantas dibuatkan class baru permanen.

Referensi Lanjutan #

Ringkasan #

Anonymous object dengan object : Type { ... } membuat instance dari class anonim yang mengimplementasikan interface atau mewarisi class open. Anonymous object cocok untuk implementasi ad-hoc seperti callback listener, instance factory sementara, dan handler event. Anonymous object dapat mengimplementasikan beberapa interface sekaligus dan menangkap variabel dari scope sekitar. Pada chapter data class berikutnya, kita akan melihat class khusus Kotlin yang otomatis menghasilkan method equals, hashCode, dan toString.