Sebelum Kotlin 1.7, builder seperti buildList { ... } sering membutuhkan anotasi tipe eksplisit — mis. buildList<String> { add("a") } — karena kompilator belum bisa menyimpulkan tipe elemen dari isi lambda. Builder inference mengatasi masalah ini: kompilator menyimpulkan tipe parameter generik dari pemanggilan method di dalam lambda body. Pada chapter ini kita akan memahami cara kerja builder inference, menulis builder kustom yang memanfaatkannya, dan mengenali batasannya.
Cara Kerja Builder Inference #
Builder inference bekerja dengan menganalisis pemanggilan method di dalam lambda yang menerima receiver bertipe TBuilder<T>. Kompilator mengumpulkan batasan tipe dari setiap pemanggilan dan menyimpulkan T:
add("halo")→TminimalStringadd(42)→TminimalInt(atauAnybila campuran)put("kunci", "nilai")→KminimalString,VminimalString
Hasilnya, kita bisa menulis buildList { add("a"); add("b") } tanpa anotasi tipe eksplisit. Kompilator tahu T = String dari pemanggilan add.
Contoh Konkret #
Cuplikan berikut menunjukkan beberapa skenario builder inference yang umum:
// builder_inference.kt
// Mendemonstrasikan builder inference — kemampuan kompilator
// menyimpulkan tipe parameter generik dari isi lambda body.
// Fitur stabil sejak Kotlin 1.7; membuat buildList, buildMap, dst nyaman dipakai.
// 1. buildList: tipe elemen disimpulkan dari add() di dalam lambda
fun contohBuildList(): List<Any> {
val list = buildList {
add("Budi")
add("Siti")
add(2026) // menyebabkan tipe elemen menjadi Any
add(true)
}
return list
}
// 2. buildList dengan tipe eksplisit — kompilator memverifikasi setiap add()
fun contohBuildListEksplisit(): List<String> = buildList {
add("Budi")
add("Siti")
add("Dewi")
// add(2026) // <- akan gagal compile karena tipe parameter String
}
// 3. buildMap: kunci dan nilai disimpulkan dari lambda
fun contohBuildMap(): Map<String, Int> = buildMap {
put("Budi", 25)
put("Siti", 30)
put("Dewi", 28)
}
// 4. Builder kustom: manfaatkan builder inference untuk DSL
class TabelBuilder<T> {
private val baris = mutableListOf<List<T>>()
fun baris(vararg sel: T) { baris.add(sel.toList()) }
fun build(): List<List<T>> = baris.toList()
}
fun <T> buildTabel(block: TabelBuilder<T>.() -> Unit): List<List<T>> {
val builder = TabelBuilder<T>()
builder.block()
return builder.build()
}
// 5. Builder inference untuk type parameter kompleks
fun <T, R> applyTwice(x: T, f: (T) -> R, g: (R) -> R): R = g(f(x))
fun main() {
println("=== 1. buildList: tipe elemen Any ===")
val campuran = contohBuildList()
println("List: $campuran")
println("Tipe elemen: ${campuran.first()::class.simpleName}, ${campuran[2]::class.simpleName}")
println()
println("=== 2. buildList dengan tipe eksplisit ===")
val nama = contohBuildListEksplisit()
println("Nama: $nama (ukuran=${nama.size})")
println()
println("=== 3. buildMap ===")
val umur = contohBuildMap()
umur.forEach { (k, v) -> println(" $k -> $v") }
println()
println("=== 4. Builder kustom memanfaatkan builder inference ===")
val tabel = buildTabel {
baris("Budi", "Jakarta", "1998")
baris("Siti", "Bandung", "2001")
baris("Dewi", "Surabaya", "1995")
}
tabel.forEachIndexed { i, r -> println(" baris ${i + 1}: $r") }
println()
println("=== 5. Builder inference dengan type parameter kompleks ===")
val hasil = applyTwice("halo", { it.length }, { it * 2 })
println("applyTwose(\"halo\", length, *2) = $hasil")
println()
println("=== 6. Tanpa builder inference (perbandingan) ===")
// Versi explicit type parameter
val eksplisit: List<String> = buildList {
add("alpha")
add("beta")
}
println("Eksplisit: $eksplisit")
}Output yang Dihasilkan:
=== 1. buildList: tipe elemen Any ===
List: [Budi, Siti, 2026, true]
Tipe elemen: String, Int
=== 2. buildList dengan tipe eksplisit ===
Nama: [Budi, Siti, Dewi] (ukuran=3)
=== 3. buildMap ===
Budi -> 25
Siti -> 30
Dewi -> 28
=== 4. Builder kustom memanfaatkan builder inference ===
baris 1: [Budi, Jakarta, 1998]
baris 2: [Siti, Bandung, 2001]
baris 3: [Dewi, Surabaya, 1995]
=== 5. Builder inference dengan type parameter kompleks ===
applyTwose("halo", length, *2) = 8
=== 6. Tanpa builder inference (perbandingan) ===
Eksplisit: [alpha, beta]Pada cuplikan di atas, buildList { add("Budi"); add("Siti"); add(2026) } menghasilkan List<Any> karena campuran tipe. buildList<String> { add("Budi") } (dengan anotasi eksplisit) menghasilkan List<String> — kompilator memverifikasi setiap add() sesuai tipe parameter. buildTabel { baris("Budi", "Jakarta", "1998") } menghasilkan List<List<String>> karena kompilator menyimpulkan T = String dari pemanggilan baris().
Builder Kustom #
Kita bisa menulis builder kustom sendiri yang memanfaatkan builder inference. Kuncinya adalah menyediakan fungsi extension pada receiver builder:
class MenuBuilder<T> {
private val item = mutableListOf<T>()
fun tambah(x: T) { item.add(x) }
fun build(): List<T> = item.toList()
}
fun <T> buildMenu(block: MenuBuilder<T>.() -> Unit): List<T> {
val b = MenuBuilder<T>()
b.block()
return b.build()
}
// Pemakaian — tipe T disimpulkan dari lambda body
val menu = buildMenu {
tambah("Nasi Goreng")
tambah("Mie Ayam")
}
// Tipe: List<String>Karena tambah("Nasi Goreng") dipanggil di lambda body, kompilator tahu T = String. Tanpa builder inference, kita harus menulis buildMenu<String> { ... } secara eksplisit.
Batasan Builder Inference #
Builder inference tidak sempurna. Beberapa batasan yang harus diingat:
- Hanya berlaku untuk lambda dengan receiver — fungsi biasa tanpa receiver tidak bisa memanfaatkan builder inference.
- Tipe ambigu bila lambda body kosong — bila tidak ada pemanggilan method yang memberi batasan tipe, kompilator tidak bisa menyimpulkan
Tdan butuh anotasi eksplisit. - Tidak menangani kondisi runtime — bila ada branch
if (cond) add("a") else add(42), kompilator menyimpulkanT = Any(atauSerializable & Comparable<...>), bukan union. - Inferensi multi-tahap — bila builder kembali memanggil builder lain, inferensi bisa gagal dan butuh anotasi.
Ringkasan #
Builder inference adalah peningkatan kenyamanan, bukan fitur fungsional baru. Apapun yang bisa ditulis dengan builder inference juga bisa ditulis dengan anotasi tipe eksplisit. Builder inference hanya membuat kode lebih ringkas dan lebih deklaratif. Gunakan builder kustom untuk pola DSL yang sering muncul di kode kita — mis. query builder, validator, atau serializer.
Referensi Lanjutan #
- What’s new in Kotlin 2.0 — catatan rilis Kotlin 2.0 yang menstabilkan builder inference tanpa anotasi eksplisit
- Kotlin documentation home
— halaman utama dokumentasi Kotlin sebagai rujukan pola
buildListdanbuildMap
Selanjutnya #
Setelah memahami builder inference, kita beranjak ke topik yang lebih besar — K2 compiler — arsitektur frontend baru yang membuat kompilasi Kotlin lebih cepat dan inferensi tipe lebih akurat.