Lambda sangat berguna untuk function sekali pakai, tetapi kadang kita ingin meneruskan function yang sudah dideklarasikan sebelumnya. Menulis ulang sebagai lambda akan menambah duplikasi dan kehilangan nama yang bermakna. Kotlin menyediakan function references dengan operator :: yang mengubah function menjadi nilai bertipe function. Pada chapter ini kita akan melihat bentuk unbound dan bound function reference.

Referensi Function Top-Level #

Function yang dideklarasikan di level top-level (di luar class) dapat dirujuk dengan ::namaFunction. Hasilnya adalah nilai bertipe function yang dapat disimpan ke variabel atau diteruskan sebagai argumen.

kotlin
fun ganjil(n: Int): Boolean = n % 2 == 1

fun isPanjang(s: String): Boolean = s.length >= 5

fun main() {
    val cekGanjil: (Int) -> Boolean = ::ganjil
    println("Apakah 7 ganjil? ${cekGanjil(7)}")
    println("Apakah 8 ganjil? ${cekGanjil(8)}")

    val predikat: (String) -> Boolean = ::isPanjang
    val kata = listOf("Budi", "Kotlin", "Siti", "Pemrograman")
    val panjang = kata.filter(predikat)
    println("Kata panjang: $panjang")

    val panjangNama: (String) -> Int = String::length
    println("Panjang kata 'Kotlin': ${panjangNama("Kotlin")}")

    val nilai = listOf(1, 2, 3, 4, 5, 6)
    val hasilGanjil = nilai.filter(::ganjil)
    println("Bilangan ganjil: $hasilGanjil")
}

Output yang Dihasilkan:

text
Apakah 7 ganjil? true
Apakah 8 ganjil? false
Kata panjang: [Kotlin, Pemrograman]
Panjang kata 'Kotlin': 6
Bilangan ganjil: [1, 3, 5]

Mari kita bedah baris per baris:

  1. val cekGanjil: (Int) -> Boolean = ::ganjil: operator :: mengubah function ganjil menjadi nilai bertipe (Int) -> Boolean. Nilai ini dapat dipanggil seperti function biasa dengan cekGanjil(7).
  2. kata.filter(predikat): function reference ::isPanjang diteruskan sebagai argumen filter. Lambda tidak perlu ditulis ulang — kode lebih ringkas dan tetap menggunakan nama function yang bermakna.
  3. String::length: referensi member function length pada class String. Bentuk ini menghasilkan nilai bertipe String.() -> Int atau (String) -> Int tergantung konteks.
  4. nilai.filter(::ganjil): function reference dapat diteruskan langsung tanpa variabel perantara. Bentuk ini paling ringkas untuk function yang dipakai sekali.

Bound vs Unbound #

Function reference ke member function memiliki dua bentuk: unbound (belum terikat instance) dan bound (sudah terikat ke instance tertentu). Bentuk unbound menggunakan Kelas::method, sedangkan bentuk bound menggunakan instance::method.

kotlin Contoh: unbound vs bound reference
class Pemohon(val nama: String) {
    fun salam() = "Halo, saya $nama"
}

fun main() {
    // Unbound: butuh instance saat dipanggil
    val refUnbound: Pemohon.() -> String = Pemohon::salam
    val p = Pemohon("Budi")
    println(p.refUnbound())   // "Halo, saya Budi"

    // Bound: instance sudah dibekukan
    val refBound: () -> String = p::salam
    println(refBound())       // "Halo, saya Budi"
}

Pada bentuk unbound, tipe function adalah Pemohon.() -> String — function dengan receiver. Saat dipanggil, instance penerima wajib disediakan. Pada bentuk bound, instance p sudah ditangkap sehingga tipe function menjadi () -> String tanpa parameter tambahan.

Referensi Constructor #

Operator :: juga dapat diterapkan pada constructor. Hasilnya adalah function yang membuat instance baru saat dipanggil. Pola ini sering dipakai sebagai factory function pada pola desain factory atau builder.

kotlin Contoh: referensi constructor
data class Pengguna(val nama: String, val umur: Int)

fun main() {
    val buat: (String, Int) -> Pengguna = ::Pengguna
    val p = buat("Budi", 25)
    println(p)
}

::Pengguna menghasilkan function (String, Int) -> Pengguna. Pemanggilan buat("Budi", 25) sama dengan Pengguna("Budi", 25). Pola ini berguna saat kita ingin meneruskan constructor sebagai argumen ke higher-order function.

Kapan Memakai Reference vs Lambda #

Pilihan antara function reference dan lambda bergantung pada konteks:

  • function sudah dideklarasikan dengan nama bermakna → pakai ::nama agar kode lebih jelas
  • logika hanya dipakai sekali dan pendek → lambda inline sering kali lebih ringkas
  • lambda memerlukan parameter tambahan yang tidak ada di function asli → lambda ekspres
  • kode membaca lebih natural dengan trailing lambda → lambda
kotlin Perbandingan: reference vs lambda
fun isPositif(n: Int) = n > 0

fun main() {
    val data = listOf(-3, -1, 0, 2, 5)

    val a = data.filter(::isPositif)        // ringkas, jelas
    val b = data.filter { it > 0 }          // lambda inline, sama hasilnya

    println(a)
    println(b)
}

Kedua pemanggilan di atas menghasilkan output yang sama. Pilihan bergantung pada apakah isPositif sudah ada di codebase atau hanya dibutuhkan sekali di tempat ini.

Referensi Lanjutan #

Ringkasan #

Function references dengan operator :: mengubah function yang sudah dideklarasikan menjadi nilai bertipe function. Bentuk unbound (Kelas::method) menghasilkan function dengan receiver, sedangkan bentuk bound (instance::method) menghasilkan function tanpa parameter tambahan karena instance sudah ditangkap. Operator :: juga dapat diterapkan pada constructor untuk membuat factory function. Pada chapter inline Functions berikutnya, kita akan melihat bagaimana keyword inline mengurangi overhead alokasi lambda pada higher-order function.