Setelah bab lalu membahas bagaimana Kotlin memodelkan data lewat data class dan sealed class, sekarang kita masuk ke sisi lain dari bahasa ini: bagaimana Kotlin memperlakukan fungsi sebagai nilai yang bisa dilempar, disimpan, dan dikombinasikan — fondasi gaya functional programming yang bikin kode Kotlin sering jauh lebih ringkas dibanding pendekatan OOP murni.
Dua Fitur, Satu Tujuan: Kode Lebih Ringkas dan Ekspresif
Kita akan bahas dua fitur lewat FunctionalBasicsTest.kt: extension function (menambah method ke tipe yang sudah ada, tanpa mewarisi atau memodifikasi class aslinya) dan higher-order function (fungsi yang menerima fungsi lain sebagai parameter). Keduanya sering dipakai bersamaan di kode backend Kotlin — dan keduanya adalah bahan baku utama fungsi-fungsi collection seperti filter/map/reduce yang akan kita bahas di bab berikutnya.
Extension Function: Menambah Method ke Tipe Milik Orang Lain
Bayangkan kamu perlu menghitung harga setelah diskon dari sebuah BigDecimal — tipe angka presisi tinggi dari library standar Java yang biasa dipakai untuk uang. BigDecimal bukan class yang kita tulis sendiri, jadi kita tidak bisa menambah method langsung ke definisinya, dan di Java biasanya solusinya adalah class utility statis seperti MoneyUtils.applyDiscount(price, percent) — dipanggil dengan urutan argumen yang harus diingat, bukan dibaca sebagai method pada objeknya.
Kotlin punya cara yang lebih natural: extension function. Kita bisa "menempelkan" method baru ke BigDecimal seolah-olah method itu memang bagian dari class-nya:
// Extension function: menambah method baru ke tipe yang sudah ada (di sini: BigDecimal),
// tanpa mewarisi atau memodifikasi class aslinya.
private fun BigDecimal.applyDiscount(percent: Int): BigDecimal {
val discountAmount = this.multiply(BigDecimal(percent)).divide(BigDecimal(100))
return this.subtract(discountAmount).setScale(2, RoundingMode.HALF_UP)
}
Perhatikan sintaksnya: fun BigDecimal.applyDiscount(...) — nama tipe yang di-extend ditulis sebelum nama fungsi, dipisah titik. Di dalam body, this merujuk ke instance BigDecimal yang memanggilnya. Hasilnya, di titik pemakaian, kode terbaca seperti method biasa:
@Test
fun `extension function bisa dipanggil seperti method biasa pada tipe yang sudah ada`() {
val price = BigDecimal("100000")
assertEquals(BigDecimal("90000.00"), price.applyDiscount(10))
}
price.applyDiscount(10) jauh lebih mudah dibaca dibanding MoneyUtils.applyDiscount(price, 10) — urutannya mengikuti alur bahasa natural ("harga, lalu terapkan diskon"), bukan urutan parameter fungsi yang harus dihafal. Penting dicatat: ini bukan pewarisan sungguhan — applyDiscount tidak benar-benar menjadi bagian dari class BigDecimal, dia cuma "gula sintaks" yang di-compile menjadi pemanggilan fungsi statis biasa di belakang layar. Tapi dari sisi keterbacaan kode, bedanya besar — dan ini salah satu alasan kenapa DTO dan domain model di Kotlin sering ditulis lebih ringkas: logika terkait sebuah tipe tidak harus semuanya ditumpuk di dalam definisi class itu sendiri.
Higher-Order Function: Fungsi Sebagai Parameter
Fitur kedua adalah higher-order function — fungsi yang salah satu (atau lebih) parameternya adalah fungsi lain. Contoh di file test ini adalah processIfValid, yang menerima dua parameter berupa fungsi: satu untuk memvalidasi, satu untuk memproses kalau validasi lolos:
// Higher-order function: menerima function lain (validator, lalu handler) sebagai parameter.
private fun processIfValid(quantity: Int, validator: (Int) -> Boolean, onValid: (Int) -> String): String =
if (validator(quantity)) onValid(quantity) else "Quantity tidak valid"
Perhatikan tipe parameter validator: (Int) -> Boolean — ini artinya "fungsi yang menerima satu Int dan mengembalikan Boolean". Kotlin menuliskan tipe fungsi persis seperti signature-nya, jadi cukup jelas dibaca tanpa perlu interface terpisah seperti Predicate<Integer> di Java. Begini pemakaiannya:
@Test
fun `higher-order function menerima lambda sebagai parameter`() {
val isPositive: (Int) -> Boolean = { it > 0 }
val result = processIfValid(3, isPositive) { qty -> "Order dengan quantity $qty diterima" }
assertEquals("Order dengan quantity 3 diterima", result)
val rejected = processIfValid(-1, isPositive) { qty -> "Order dengan quantity $qty diterima" }
assertEquals("Quantity tidak valid", rejected)
}
Ada dua hal untuk diperhatikan di sini. Pertama, isPositive dideklarasikan sebagai lambda — sebuah literal fungsi anonim, { it > 0 }, di mana it adalah nama parameter implisit saat lambda hanya punya satu parameter. Kedua, perhatikan cara processIfValid dipanggil: processIfValid(3, isPositive) { qty -> ... } — parameter fungsi terakhir (onValid) ditulis di luar tanda kurung, sebagai blok kode setelah pemanggilan. Ini disebut trailing lambda syntax, dan Kotlin mengizinkannya khusus untuk parameter fungsi yang posisinya terakhir. Kalau kamu pernah melihat kode Kotlin yang polanya mirip someFunction(arg) { ... }, itulah trailing lambda — sangat umum dipakai di DSL Kotlin (termasuk konfigurasi Spring Boot dengan Kotlin DSL nanti).
Kenapa Ini Penting untuk Backend
Tanpa higher-order function, pola "validasi lalu proses" biasanya berakhir jadi banyak method overload, atau interface terpisah yang harus diimplementasikan tiap kali perilakunya berbeda (pikirkan Comparator atau Runnable di Java sebelum lambda ada) — lapisan boilerplate yang menjauhkan logika inti dari yang terlihat di layar. Di Kotlin, logika seperti "aturan validasi apa yang dipakai" dan "apa yang terjadi kalau valid" bisa dilempar langsung sebagai argumen, ditulis persis di titik pemanggilan, tanpa perlu class terpisah untuk setiap kombinasi perilaku.
Kombinasi extension function dan higher-order function inilah yang jadi mesin di balik operasi collection seperti filter, map, dan reduce — yang justru semuanya adalah extension function bertipe higher-order pada List. Itu topik bab berikutnya, di mana kita akan pakai kombo ini untuk memproses daftar item belanja secara nyata.
Bagian dari Series: Kotlin Backend Microservice - Belajar Fundamental sampai Microservice Nyata
Belajar Kotlin + Spring Boot dari fundamental bahasa (null safety, OOP, coroutines) sampai membangun sistem microservice sungguhan - REST API, Postgre...
Lihat Series Lengkap