Fungsi

Fungsi #

Fungsi adalah unit terkecil dari kode yang bisa diberi nama, dipanggil berkali-kali, dan menerima input serta menghasilkan output. Di Kotlin, fungsi adalah warga kelas satu (first-class citizen) — mereka bisa disimpan dalam variabel, diteruskan sebagai argumen, dan dikembalikan dari fungsi lain. Ini membuka paradigma pemrograman fungsional yang membuat Kotlin sangat ekspresif. Artikel ini membahas semua bentuk fungsi di Kotlin secara mendalam: dari deklarasi dasar, fitur-fitur yang mengurangi boilerplate seperti default parameter dan single-expression, hingga konsep lanjutan seperti higher-order function, lambda, tail recursion, dan scope function.

Fungsi Dasar #

Fungsi dideklarasikan dengan kata kunci fun, diikuti nama, daftar parameter dalam kurung, tipe kembalian setelah :, dan body dalam kurung kurawal.

fun sapa(nama: String): String {
    return "Halo, $nama!"
}

fun tambah(a: Int, b: Int): Int {
    return a + b
}

// Fungsi tanpa return value — Unit bisa dihilangkan
fun cetak(pesan: String): Unit {
    println(pesan)
}

fun cetak2(pesan: String) {  // Unit tersirat
    println(pesan)
}

Cara memanggil fungsi tidak ada kejutannya:

val sapaan = sapa("Budi")      // "Halo, Budi!"
val hasil  = tambah(10, 25)    // 35
cetak("Selamat datang!")

Single-Expression Function #

Jika body fungsi hanya satu ekspresi, kamu bisa menulisnya dengan bentuk yang jauh lebih ringkas menggunakan = tanpa kurung kurawal dan tanpa return:

// Bentuk panjang
fun kuadrat(x: Int): Int {
    return x * x
}

// Single-expression — identik, tapi lebih ringkas
fun kuadrat(x: Int): Int = x * x

// Tipe kembalian bisa diinfer jika ekspresinya jelas
fun kuadrat(x: Int) = x * x

// Contoh lain
fun maks(a: Int, b: Int) = if (a > b) a else b
fun isGanjil(n: Int) = n % 2 != 0
fun salam(nama: String) = "Halo, $nama! Selamat datang."

Gunakan single-expression untuk fungsi yang benar-benar sederhana dan bisa dibaca dalam satu baris. Jangan paksakan bentuk ini untuk logika yang butuh beberapa langkah — itu justru mengurangi keterbacaan.


Default Parameter #

Parameter bisa punya nilai default yang digunakan ketika argumen tidak diberikan saat pemanggilan. Ini menghilangkan kebutuhan function overloading yang berlebihan.

fun buatHeader(
    judul: String,
    level: Int = 1,
    kapital: Boolean = false
): String {
    val teksJudul = if (kapital) judul.uppercase() else judul
    val tanda = "#".repeat(level)
    return "$tanda $teksJudul"
}

println(buatHeader("Pengantar"))                          // # Pengantar
println(buatHeader("Pengantar", level = 2))               // ## Pengantar
println(buatHeader("Pengantar", level = 3, kapital = true)) // ### PENGANTAR

Menghindari Overloading dengan Default Parameter #

// ANTI-PATTERN di Java: overloading untuk nilai opsional
fun kirimEmail(ke: String, subjek: String, isi: String) { ... }
fun kirimEmail(ke: String, subjek: String) { kirimEmail(ke, subjek, "") }
fun kirimEmail(ke: String) { kirimEmail(ke, "Tanpa Subjek", "") }

// BENAR di Kotlin: satu fungsi dengan default parameter
fun kirimEmail(
    ke: String,
    subjek: String = "Tanpa Subjek",
    isi: String = "",
    cc: List<String> = emptyList(),
    prioritas: String = "normal"
) {
    // implementasi
}

// Panggil dengan subset argumen
kirimEmail("[email protected]")
kirimEmail("[email protected]", subjek = "Penting")
kirimEmail("[email protected]", prioritas = "tinggi")

Named Argument #

Named argument memungkinkan kamu menyebut nama parameter saat memanggil fungsi. Ini membuat urutan argumen menjadi bebas dan kode lebih eksplisit.

fun buatPengguna(
    nama: String,
    umur: Int,
    email: String,
    aktif: Boolean = true
): String = "[$nama | $umur | $email | aktif=$aktif]"

// Tanpa named argument — urutan harus tepat
buatPengguna("Budi", 25, "[email protected]")

// Dengan named argument — urutan bebas, maksud lebih jelas
buatPengguna(
    nama = "Budi",
    email = "[email protected]",
    umur = 25,
    aktif = false
)

Named argument sangat berguna untuk fungsi dengan banyak parameter Boolean atau parameter yang tipenya sama, di mana posisi saja tidak cukup untuk memberi tahu pembaca apa arti tiap argumen:

// ANTI-PATTERN: ambiguous — apa arti true, false, true ini?
konfigurasikan("server1", true, false, true, 5000)

// BENAR: eksplisit dengan named argument
konfigurasikan(
    host = "server1",
    ssl = true,
    debug = false,
    cacheEnabled = true,
    port = 5000
)

Vararg — Jumlah Argumen Variabel #

vararg memungkinkan fungsi menerima jumlah argumen yang tidak terbatas untuk satu parameter. Di dalam fungsi, parameter vararg diperlakukan sebagai Array.

fun jumlahkan(vararg angka: Int): Int = angka.sum()

println(jumlahkan(1, 2, 3))           // 6
println(jumlahkan(10, 20, 30, 40))    // 100
println(jumlahkan())                  // 0

fun gabungkan(pemisah: String, vararg kata: String): String {
    return kata.joinToString(pemisah)
}

println(gabungkan(", ", "Kotlin", "Java", "Go"))  // Kotlin, Java, Go

Untuk meneruskan array ke parameter vararg, gunakan operator spread (*):

val daftar = intArrayOf(1, 2, 3, 4, 5)
println(jumlahkan(*daftar))   // 15 — spread array ke vararg

Fungsi Lokal #

Kotlin mengizinkan fungsi dideklarasikan di dalam fungsi lain. Fungsi lokal bisa mengakses variabel dari fungsi yang melingkupinya (closure).

fun validasiFormulir(nama: String, email: String, umur: Int): List<String> {
    val kesalahan = mutableListOf<String>()

    // Fungsi lokal — hanya bisa diakses di dalam validasiFormulir
    fun tambahKesalahan(pesan: String) {
        kesalahan.add(pesan)
    }

    fun String.tidakValid() = this.isBlank()

    if (nama.tidakValid()) tambahKesalahan("Nama tidak boleh kosong")
    if (email.tidakValid() || !email.contains("@")) {
        tambahKesalahan("Email tidak valid")
    }
    if (umur < 0 || umur > 150) tambahKesalahan("Umur tidak masuk akal: $umur")

    return kesalahan
}

val masalah = validasiFormulir("", "bukan-email", -5)
masalah.forEach { println("• $it") }
// • Nama tidak boleh kosong
// • Email tidak valid
// • Umur tidak masuk akal: -5

Fungsi lokal ideal untuk memecah logika yang terlalu panjang di dalam sebuah fungsi, tanpa mengekspos helper function tersebut ke scope yang lebih luas.


Fungsi Infix #

Fungsi infix bisa dipanggil tanpa titik dan tanpa kurung — seperti operator. Tiga syaratnya: harus berupa member function atau extension function, hanya memiliki satu parameter, dan parameter tersebut tidak boleh vararg atau punya default value.

infix fun Int.kaliLipat(n: Int): Int = this * n

println(5 kaliLipat 3)   // 15
println(2 kaliLipat 10)  // 20

// Contoh lebih deskriptif
infix fun String.tidakSamaDengan(lain: String) = this != lain

println("kotlin" tidakSamaDengan "java")   // true
println("kotlin" tidakSamaDengan "kotlin") // false

// Membuat DSL sederhana
data class Rentang(val awal: Int, val akhir: Int)
infix fun Int.hingga(akhir: Int) = Rentang(this, akhir)

val rentangKerja = 9 hingga 17
println("${rentangKerja.awal}:00 – ${rentangKerja.akhir}:00")  // 9:00 – 17:00

Fungsi Ekstensi #

Extension function memungkinkan kamu menambahkan fungsi ke kelas yang sudah ada — termasuk kelas dari library pihak ketiga atau standard library — tanpa inheritance atau modifikasi kelas aslinya.

// Tambahkan ke String
fun String.namaDepan(): String = this.trim().split(" ").first()
fun String.namaAkhir(): String = this.trim().split(" ").last()
fun String.inisial(): String = this.trim().split(" ").joinToString("") { it.first().uppercase() }

val namaLengkap = "  Budi Santoso Wijaya  "
println(namaLengkap.namaDepan())  // Budi
println(namaLengkap.namaAkhir())  // Wijaya
println(namaLengkap.inisial())    // BSW

// Tambahkan ke Int
fun Int.rupiah(): String = "Rp%,d".format(this)
fun Int.ribuan(): String = "%,d".format(this)

println(1_500_000.rupiah())  // Rp1,500,000
println(9_876_543.ribuan())  // 9,876,543

// Tambahkan ke List
fun <T> List<T>.keduaAtauNull(): T? = if (size >= 2) this[1] else null

val daftar = listOf("pertama", "kedua", "ketiga")
println(daftar.keduaAtauNull())           // kedua
println(emptyList<String>().keduaAtauNull())  // null

Extension function dikompilasi menjadi static method di JVM — ia tidak benar-benar mengubah kelas target dan tidak bisa mengakses member private-nya.


Lambda dan Fungsi Anonim #

Lambda adalah fungsi anonim yang bisa disimpan dalam variabel atau diteruskan sebagai argumen. Ini adalah fondasi dari pemrograman fungsional di Kotlin.

// Lambda disimpan dalam variabel
val kuadrat: (Int) -> Int = { x -> x * x }
val sapa: (String) -> String = { nama -> "Halo, $nama!" }
val cetak: (String) -> Unit = { teks -> println(teks) }

// Implicit parameter 'it' ketika hanya satu parameter
val ganda: (Int) -> Int = { it * 2 }
val isPositif: (Int) -> Boolean = { it > 0 }

println(kuadrat(5))    // 25
println(sapa("Rina"))  // Halo, Rina!
println(ganda(7))      // 14

Tipe Fungsi #

Tipe fungsi ditulis sebagai (TipeParameter) -> TipeKembalian:

val aksi: () -> Unit              // tidak ada parameter, tidak ada return
val konversi: (String) -> Int     // satu parameter String, kembalikan Int
val operasi: (Int, Int) -> Int    // dua parameter Int, kembalikan Int
val predikat: (String) -> Boolean // satu parameter String, kembalikan Boolean

Trailing Lambda #

Ketika parameter terakhir sebuah fungsi adalah lambda, kamu bisa menulisnya di luar kurung pemanggilan:

fun ulangi(kali: Int, aksi: () -> Unit) {
    for (i in 1..kali) aksi()
}

// Tanpa trailing lambda
ulangi(3, { println("Halo!") })

// Dengan trailing lambda — lebih bersih
ulangi(3) { println("Halo!") }

// Jika lambda adalah satu-satunya argumen, kurung bisa dihilangkan
listOf(1, 2, 3).forEach { println(it) }

Fungsi Anonim #

Fungsi anonim mirip lambda tapi menggunakan sintaks fun secara eksplisit. Berguna ketika tipe kembalian perlu ditulis secara eksplisit atau ketika butuh return dari fungsi itu sendiri (bukan dari fungsi yang melingkupi):

val kalikan = fun(a: Int, b: Int): Int {
    return a * b
}

// Lambda tidak bisa mendeklarasikan return type secara eksplisit
// Fungsi anonim bisa — berguna untuk tipe kompleks
val proses = fun(input: String): String? {
    if (input.isBlank()) return null
    return input.trim().uppercase()
}

Higher-Order Function #

Higher-order function adalah fungsi yang menerima fungsi lain sebagai parameter, atau mengembalikan fungsi sebagai hasil. Ini adalah konsep kunci yang memungkinkan abstraksi yang sangat kuat.

// Menerima fungsi sebagai parameter
fun lakukanOperasi(a: Int, b: Int, operasi: (Int, Int) -> Int): Int {
    return operasi(a, b)
}

println(lakukanOperasi(10, 5) { x, y -> x + y })   // 15
println(lakukanOperasi(10, 5) { x, y -> x * y })   // 50
println(lakukanOperasi(10, 5) { x, y -> x - y })   // 5

// Mengembalikan fungsi sebagai hasil
fun buatPengali(faktor: Int): (Int) -> Int {
    return { angka -> angka * faktor }
}

val kaliDua   = buatPengali(2)
val kaliTiga  = buatPengali(3)
val kaliSepuluh = buatPengali(10)

println(kaliDua(7))      // 14
println(kaliTiga(7))     // 21
println(kaliSepuluh(7))  // 70

Komposisi Fungsi #

Higher-order function memungkinkan komposisi — menggabungkan fungsi-fungsi kecil menjadi pipeline:

fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C = { f(g(it)) }

val tambahSatu: (Int) -> Int = { it + 1 }
val kaliDua: (Int) -> Int = { it * 2 }
val kuadrat: (Int) -> Int = { it * it }

val tambahSatuLaluKaliDua = compose(kaliDua, tambahSatu)
val kaliDuaLaluTambahSatu = compose(tambahSatu, kaliDua)

println(tambahSatuLaluKaliDua(5))  // (5+1)*2 = 12
println(kaliDuaLaluTambahSatu(5))  // (5*2)+1 = 11

Tail Recursion — Rekursi yang Aman #

Rekursi biasa berisiko StackOverflowError untuk input yang sangat besar karena setiap pemanggilan menambah frame ke call stack. Kotlin mendukung tail recursion dengan kata kunci tailrec — compiler mengoptimalkannya menjadi loop di belakang layar, sehingga call stack tidak bertambah.

// ANTI-PATTERN: rekursi biasa — StackOverflow untuk n besar
fun faktorialBiasa(n: Long): Long {
    return if (n <= 1) 1 else n * faktorialBiasa(n - 1)
}

// BENAR: tail recursion dengan tailrec
tailrec fun faktorial(n: Long, akumulator: Long = 1): Long {
    return if (n <= 1) akumulator else faktorial(n - 1, n * akumulator)
}

println(faktorial(10))    // 3628800
println(faktorial(20))    // 2432902008176640000
// faktorial(100000) pun aman — tidak ada StackOverflow!

// Fibonacci dengan tail recursion
tailrec fun fibonacci(n: Int, a: Long = 0, b: Long = 1): Long {
    return when (n) {
        0    -> a
        1    -> b
        else -> fibonacci(n - 1, b, a + b)
    }
}

println(fibonacci(50))  // 12586269025

Syarat tailrec: panggilan rekursif harus menjadi operasi terakhir yang dilakukan fungsi — tidak boleh ada perhitungan setelah panggilan rekursif.


Scope Functions — let, run, with, apply, also #

Scope function adalah higher-order function bawaan Kotlin yang mengeksekusi blok kode dalam konteks sebuah objek. Mereka mengurangi kebutuhan variabel sementara dan membuat kode lebih ringkas.

data class Pengguna(var nama: String, var email: String, var aktif: Boolean = false)

// let — transformasi objek, cocok untuk nullable check
val panjangNama = "  Budi Santoso  ".let { it.trim().length }
println(panjangNama)  // 11

val pengguna: Pengguna? = ambilPengguna()
pengguna?.let {
    println("Halo, ${it.nama}!")  // hanya dieksekusi jika tidak null
}

// apply — konfigurasi objek, mengembalikan objek itu sendiri
val penggunaBaru = Pengguna("", "").apply {
    nama = "Sari Dewi"
    email = "[email protected]"
    aktif = true
}

// also — side effect (logging, debugging), mengembalikan objek
val daftar = mutableListOf(3, 1, 4, 1, 5, 9)
    .also { println("Sebelum sort: $it") }
    .also { it.sort() }
    .also { println("Setelah sort: $it") }

// with — operasi pada objek tanpa perlu sebut nama objeknya
val ringkasan = with(penggunaBaru) {
    "Pengguna: $nama | Email: $email | Aktif: $aktif"
}
println(ringkasan)
FungsiKonteks (this/it)Mengembalikan
letitHasil lambda
runthisHasil lambda
withthisHasil lambda
applythisObjek itu sendiri
alsoitObjek itu sendiri

Ringkasan #

  • Single-expression function — gunakan fun nama(param) = ekspresi untuk fungsi yang hasilnya adalah satu ekspresi. Lebih ringkas dari blok { return ... }.
  • Default parameter menggantikan overloading — daripada mendefinisikan banyak fungsi dengan tanda tangan berbeda, cukup satu fungsi dengan nilai default untuk parameter opsional.
  • Named argument untuk kejelasan — selalu gunakan named argument ketika fungsi punya banyak parameter dengan tipe sama atau banyak Boolean, agar pembaca tahu apa arti tiap argumen.
  • Fungsi lokal untuk memecah logika — jika helper function hanya dibutuhkan dalam satu fungsi, deklarasikan sebagai fungsi lokal. Ia bisa mengakses variabel dari scope luar.
  • Extension function tanpa inheritance — tambahkan kemampuan baru ke kelas apapun tanpa mewarisi atau memodifikasi kelas tersebut.
  • Lambda adalah nilai — lambda bisa disimpan, diteruskan, dan dikembalikan seperti nilai apapun. Tipe fungsinya ditulis sebagai (TipeParam) -> TipeHasil.
  • Trailing lambda untuk keterbacaan — ketika argumen terakhir adalah lambda, letakkan di luar kurung untuk gaya yang lebih bersih dan lebih mirip struktur kontrol bawaan.
  • tailrec untuk rekursi aman — tandai fungsi rekursif dengan tailrec agar compiler mengoptimalkannya menjadi loop, menghindari StackOverflowError untuk input besar. Panggilan rekursif harus menjadi operasi terakhir.
  • Scope function untuk kode yang lebih ringkaslet untuk nullable dan transformasi, apply untuk konfigurasi objek, also untuk side effect, with untuk operasi dalam konteks objek.

← Sebelumnya: Perulangan   Berikutnya: Kelas →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact