Extension lambda adalah lambda yang berperilaku seperti extension function — disimpan dalam variabel bertipe receiver function type dan dapat dipanggil pada instance receiver. Pada chapter ini kita akan melihat bagaimana extension lambda menggabungkan dua konsep: extension function (member tambahan pada tipe yang sudah ada) dan lambda (function literal yang dapat disimpan dan diteruskan). Kita juga akan melihat bagaimana pola ini menjadi dasar scope function apply dan run, serta function standard library buildString.

Apa Itu Extension Lambda #

Extension lambda adalah variabel yang bertipe receiver function type A.() -> B. Variabel ini dapat dipanggil pada instance A layaknya extension function, dan di dalam body, this merujuk ke instance receiver tersebut.

kotlin Bentuk extension lambda
val panjang2x: String.() -> Int = { this.length * 2 }

// Pemanggilan seperti extension function:
println("Budi".panjang2x())  // 8

Beda dengan extension function biasa (fun String.panjang2x(): Int { ... }), extension lambda disimpan dalam variabel dan dapat diteruskan sebagai argumen ke function lain.

Contoh Extension Lambda #

Mari kita lihat extension lambda dalam beberapa skenario — dari perhitungan sederhana hingga builder block.

kotlin
// extension_lambdas.kt
// Extension lambda: kombinasi extension function dengan lambda.
// Disimpan dalam variabel bertipe receiver function type.

// Lambda dengan receiver String — di dalam body, `this` adalah String.
val panjang2x: String.() -> Int = { this.length * 2 }

// Lambda dengan receiver List<Int> untuk perhitungan.
val jumlahkan: List<Int>.() -> Int = {
    var total = 0
    for (e in this) total += e
    total
}

// Lambda receiver yang mengembalikan String baru.
val kapitalisasi: String.() -> String = {
    this.split(" ").joinToString(" ") { kata ->
        kata.replaceFirstChar { it.uppercase() }
    }
}

// Lambda receiver sebagai builder block.
val bangunTabel: StringBuilder.() -> Unit = {
    appendLine("| Nama  | Umur |")
    appendLine("|-------|------|")
    appendLine("| Budi  | 25   |")
    appendLine("| Siti  | 30   |")
}

// Higher-order function yang menerima extension lambda.
inline fun <T> T.pakai(block: T.() -> Unit): T {
    this.block()
    return this
}

fun main() {
    // Memanggil extension lambda pada instance.
    println("Panjang 2x \"Budi\": ${"Budi".panjang2x()}")

    val data = listOf(1, 2, 3, 4, 5)
    println("Total: ${data.jumlahkan()}")

    println("Kapitalisasi: ${"budi santoso".kapitalisasi()}")

    // Pakai extension lambda sebagai builder.
    val tabel = StringBuilder().apply { bangunTabel() }.toString()
    println("Tabel:")
    println(tabel)

    // pakai() mengembalikan receiver setelah block dijalankan.
    val hasil = "Halo".pakai {
        println("  Di dalam block, this = $this")
    }
    println("Hasil pakai(): $hasil")
}

Output yang Dihasilkan:

text
Panjang 2x "Budi": 8
Total: 15
Kapitalisasi: Budi Santoso
Tabel:
| Nama  | Umur |
|-------|------|
| Budi  | 25   |
| Siti  | 30   |

  Di dalam block, this = Halo
Hasil pakai(): Halo

Mari kita telaah baris-baris kode di atas:

  1. Baris 6 (val panjang2x: String.() -> Int): variabel extension lambda bertipe String.() -> Int. Di dalam body, this adalah String tempat lambda dipanggil.
  2. Baris 9 (val jumlahkan: List<Int>.() -> Int): extension lambda dengan receiver List<Int>. Body mengiterasi this (list) dan menjumlahkan elemen.
  3. Baris 16 (val kapitalisasi: String.() -> String): extension lambda yang memanipulasi string. Di dalam body, this adalah String asli — kita memanggil this.split(" ") untuk memecah string.
  4. Baris 22 (val bangunTabel: StringBuilder.() -> Unit): extension lambda sebagai builder. Body memanggil appendLine pada this (StringBuilder) — sama seperti di dalam method class StringBuilder.
  5. Baris 30 (inline fun <T> T.pakai(block: T.() -> Unit): T): higher-order function yang menerima extension lambda. Receiver T dari pakai menjadi this di dalam block. Setelah block dijalankan, function mengembalikan receiver — pola yang sama dengan apply.
  6. Baris 38 ("Budi".panjang2x()): pemanggilan extension lambda pada instance "Budi". Sintaksnya sama dengan extension function biasa.
  7. Baris 41 (data.jumlahkan()): pemanggilan pada List<Int>. Lambda jumlahkan berperilaku seolah-olah adalah member List<Int>.
  8. Baris 47 (val tabel = StringBuilder().apply { bangunTabel() }.toString()): menggunakan apply untuk menjalankan extension lambda pada instance StringBuilder. Di dalam apply, this adalah StringBuilder; kita kemudian memanggil this.bangunTabel() (ekstensi lambda pada StringBuilder).
  9. Baris 51 ("Halo".pakai { ... }): pemanggilan function pakai dengan trailing lambda. Di dalam block, this adalah "Halo". Function mengembalikan "Halo" setelah block dijalankan.

Kapan Memakai Extension Lambda #

Extension lambda cocok dipakai saat kita ingin menambah perilaku pada tipe tanpa mendefinisikan ulang tipe tersebut. Pola ini sangat berguna untuk:

  • Membuat DSL berbasis konfigurasi (builder block).
  • Mengenkapsulasi logika yang berulang pada tipe tertentu.
  • Menyediakan API yang dapat dipanggil layaknya method asli tipe tersebut.
AspekExtension Function fun X.f()Extension Lambda val f: X.() -> Y
Disimpan dalam variabelTidakYa
Bisa diteruskan sebagai argumenTidak langsungYa
Bisa dipanggil seperti methodYaYa
Memerlukan receiver thisYaYa

Referensi Lanjutan #

Penutup #

Extension lambda menggabungkan kekuatan extension function dengan fleksibilitas lambda. Kita dapat menyimpan behavior tambahan untuk tipe tertentu, meneruskannya ke function lain, dan memanggilnya seperti method asli. Pola ini menjadi dasar scope function yang akan kita bahas tuntas pada chapter-chapter selanjutnya. Sebelumnya, kita perlu memahami satu konsep terkait: closure — kemampuan lambda menangkap variabel dari lingkup luar.