Function di Kotlin dapat diperlakukan sebagai nilai — kita dapat menyimpannya di variabel, meneruskannya ke function lain, atau mengembalikannya dari function. Mekanisme ini disebut callable reference. Pada chapter ini kita akan membahas tiga bentuk callable reference — top-level function, class member (unbound), dan instance member (bound) — serta bagaimana memanfaatkan keduanya untuk membangun higher-order function yang ringkas. Pemahaman ini melengkapi pengetahuan reflection dan menjembatani ke bab tentang lambda dan higher-order function pada section lain.

Tiga Bentuk Callable Reference #

BentukSintaksTipe
Top-level function::namaFungsi(T) -> R
Class member (unbound)Class::member(Class) -> R
Instance member (bound)instance::member() -> R
Constructor::ClassName(T) -> ClassName
PropertyClass::prop atau instance::propKProperty<R> atau () -> R

Contoh Kode #

Kode berikut mendemonstrasikan ketujuh bentuk sekaligus:

kotlin
// File: callable_references.kt
// Mendemonstrasikan callable reference: ::funcName, Class::func, obj::method (bound).
package com.example.callable

import kotlin.reflect.KFunction
import kotlin.reflect.KProperty

fun sapa(nama: String): String = "Halo, $nama!"

fun gandakan(x: Int): Int = x * 2

class Pengguna(val nama: String) {
    fun halo(): String = "Halo, saya $nama"
}

fun terapkan(x: Int, op: (Int) -> Int): Int = op(x)

fun main() {
    // 1. Top-level function reference.
    val fnSapa: (String) -> String = ::sapa
    println(fnSapa("Budi"))

    val fnGanda: (Int) -> Int = ::gandakan
    println("gandakan(21) = ${terapkan(21, fnGanda)}")

    // 2. Reference ke method pada class (unbound): butuh instance sebagai receiver.
    val haloUnbound: (Pengguna) -> String = Pengguna::halo
    val p = Pengguna("Siti")
    println(haloUnbound(p))

    // 3. Bound reference: instance sudah terikat, signature tidak butuh receiver.
    val haloBound: () -> String = p::halo
    println(haloBound())

    // 4. Reference ke property (getter).
    val namaProp: KProperty<String> = Pengguna::nama
    println("namaProp.getter.call(p) = ${namaProp.getter.call(p)}")

    val namaBound: () -> String = p::nama
    println("namaBound() = ${namaBound()}")

    // 5. Reference ke constructor.
    val buatPengguna: (String) -> Pengguna = ::Pengguna
    val baru = buatPengguna("Dewi")
    println("Pengguna baru: ${baru.nama}")

    // 6. KFunction reflection — nama dan parameter.
    val kfn: KFunction<*> = ::sapa
    println("KFunction nama=${kfn.name}, param count=${kfn.parameters.size}")

    // 7. Mem-pass callable reference ke higher-order function.
    val list = listOf(1, 2, 3, 4, 5)
    val kaliDua = list.map(::gandakan)
    println("map(gandakan) = $kaliDua")
}

Output yang Dihasilkan:

text
Halo, Budi!
gandakan(21) = 42
Halo, saya Siti
Halo, saya Siti
namaProp.getter.call(p) = Siti
namaBound() = Siti
Pengguna baru: Dewi
KFunction nama=sapa, param count=1
map(gandakan) = [2, 4, 6, 8, 10]

Mari kita bedah baris-baris kunci di atas:

  1. Baris 26 (val fnSapa: (String) -> String = ::sapa): reference ke function top-level. Tipe (String) -> String sesuai signature sapa(nama: String): String. Setelah ini, fnSapa("Budi") setara dengan sapa("Budi").
  2. Baris 30 (val haloUnbound: (Pengguna) -> String = Pengguna::halo): reference ke member function halo. Karena unbound, signature adalah (Pengguna) -> String — instance wajib disertakan saat pemanggilan.
  3. Baris 34 (val haloBound: () -> String = p::halo): bound reference. Instance p sudah terikat ke reference; signature hanya () -> String. Pemanggilan haloBound() langsung mengembalikan hasil.
  4. Baris 38 (val namaProp: KProperty<String> = Pengguna::nama): reference ke property class. Tipe KProperty<String> menyimpan metadata tambahan dibanding function type biasa.
  5. Baris 42 (val namaBound: () -> String = p::nama): bound reference ke property. Signature () -> String — pemanggilan seperti function tanpa argumen.
  6. Baris 46 (val buatPengguna: (String) -> Pengguna = ::Pengguna): reference ke constructor. Memungkinkan kita membuat instance secara dinamis, berguna untuk factory pattern.
  7. Baris 50 (val kfn: KFunction<*> = ::sapa): reference sebagai KFunction untuk inspeksi reflection. kfn.name mengembalikan "sapa", kfn.parameters mengembalikan daftar parameter.
  8. Baris 54 (list.map(::gandakan)): callable reference dipakai sebagai argumen higher-order function. Lebih ringkas dibanding lambda { x -> gandakan(x) }.

Bound Reference dan Capturing #

Bound reference menangkap instance ke dalam closure. Setelah reference dibuat, instance tidak dapat diubah — bahkan bila variabel yang menampung instance di-assign ulang. Hal ini penting untuk diingat:

kotlin Bound reference menangkap instance
var p1 = Pengguna("Budi")
val halo = p1::halo
p1 = Pengguna("Siti")
println(halo())  // tetap mencetak "Halo, saya Budi"

Constructor Reference #

Reference ::ClassName menghasilkan function type yang sesuai dengan constructor utama. Untuk class dengan secondary constructor, kita dapat memilih constructor lain dengan cast eksplisit atau dengan deklarasi tipe parameter:

kotlin Constructor reference secondary
class Produk(val nama: String) {
    constructor(id: Long) : this("Produk#$id")
}

val buatDariNama: (String) -> Produk = ::Produk
// val buatDariId: (Long) -> Produk = ::Produk  // ambiguous

Kompilator memilih constructor yang paling cocok berdasarkan tipe yang dideklarasikan.

Property Reference dan KProperty #

Reference property menghasilkan KProperty (untuk val) atau KMutableProperty (untuk var). Selain sebagai function value, KProperty menyimpan metadata seperti name, isLateinit, dan returnType — berguna untuk reflection lanjutan.

Referensi Lanjutan #

Selanjutnya #

Reflection sangat berguna, tetapi membawa overhead performa. Chapter Reflection Performance akan mengukur biaya reflection dan menunjukkan cara meminimalkannya.