Scope function let adalah salah satu alat paling serbaguna untuk penanganan nullable di Kotlin. Dipadukan dengan operator safe call ?., blok let hanya dieksekusi bila penerima tidak null, dan parameter it di dalam blok sudah bertipe non-nullable. Pola ini menjadi alternatif ringkas untuk if (x != null) { ... } dan sangat berguna saat kita perlu menjalankan beberapa statement terhadap nilai nullable. Pada chapter ini kita akan membedah cara kerja ?.let, parameter it, serta perbandingannya dengan pola if eksplisit.

Cara Kerja ?.let #

let adalah function ekstensi pada tipe T yang menerima lambda (T) -> R dan mengembalikan hasil lambda. Bentuk umum: penerima?.let { it -> ... }. Bila penerima null, blok let tidak dieksekusi dan seluruh ekspresi menghasilkan null. Bila tidak null, let dipanggil dengan parameter it bertipe non-nullable.

kotlin
data class Pengguna(val nama: String, val email: String?)

fun main() {
    val nama1: String? = "Budi"
    nama1?.let {
        println("Memproses nama: $it")
        println("Panjang nama: ${it.length}")
        println("Huruf besar: ${it.uppercase()}")
    }

    val nama2: String? = null
    nama2?.let {
        println("Ini tidak akan tercetak: $it")
    }
    println("nama2 null, blok let dilewati")

    val pengguna: Pengguna? = Pengguna("Siti", "[email protected]")
    pengguna?.email?.let { alamatEmail ->
        println("Email: $alamatEmail")
        println("Domain: ${alamatEmail.substringAfter('@')}")
    }

    val penggunaLain: Pengguna? = Pengguna("Dewi", null)
    penggunaLain?.email?.let { alamatEmail ->
        println("Email Dewi: $alamatEmail")
    }
    println("Selesai memeriksa email Dewi")

    val daftar: List<String?> = listOf("Budi", null, "Siti", null, "Dewi")
    for (item in daftar) {
        item?.let { namaValid ->
            println("Nama valid ditemukan: $namaValid (panjang ${namaValid.length})")
        }
    }

    val idPengguna: Long? = 9988L
    val idFormatted: String = idPengguna?.let { "ID-$it" } ?: "TANPA-ID"
    println("ID terformat: $idFormatted")

    val konfigurasi: Map<String, String?> = mapOf("host" to "localhost", "port" to null)
    val port: Int = konfigurasi["port"]?.let { it.toIntOrNull() ?: 8080 } ?: 8080
    println("Port akhir: $port")

    val nilai: String? = "Belajar"
    nilai?.let { namaLengkap ->
        val panjang = namaLengkap.length
        val hurufKapital = namaLengkap.uppercase()
        println("Panjang=$panjang, kapital=$hurufKapital")
    }

    val opsional: String? = null
    val hasil: Int = opsional?.let { it.length } ?: 0
    println("Hasil panjang (default 0): $hasil")
}

Output yang Dihasilkan:

text
Memproses nama: Budi
Panjang nama: 4
Huruf besar: BUDI
nama2 null, blok let dilewati
Email: [email protected]
Domain: example.com
Selesai memeriksa email Dewi
Nama valid ditemukan: Budi (panjang 4)
Nama valid ditemukan: Siti (panjang 4)
Nama valid ditemukan: Dewi (panjang 4)
ID terformat: ID-9988
Port akhir: 8080
Panjang=7, kapital=BELAJAR
Hasil panjang (default 0): 0

Mari kita uraikan baris-baris penting pada contoh di atas:

  1. Baris 5-9 (nama1?.let { ... }): pola paling dasar. nama1 tidak null sehingga blok let dijalankan. Parameter it bertipe String (non-nullable), sehingga .length dan .uppercase() dapat dipanggil langsung.
  2. Baris 11-13 (nama2?.let { ... }): nama2 adalah null, sehingga blok let tidak dieksekusi sama sekali. Tidak ada pengecualian yang dilempar.
  3. Baris 16-19 (pengguna?.email?.let { alamatEmail -> ... }): safe call dirantaikan dengan let. Parameter diganti nama menjadi alamatEmail agar lebih deskriptif. Penggantian nama sangat berguna bila it terlalu generik untuk konteks blok.
  4. Baris 21-24 (penggunaLain?.email?.let { ... }): kasus di mana penggunaLain tidak null namun email null. Rantai safe call ?. menjamin blok let dilewati secara halus.
  5. Baris 28-32 (iterasi for (item in daftar)): ?.let pada iterasi elemen nullable. Untuk setiap elemen non-null, blok dijalankan; elemen null diam-diam dilewati tanpa perlu if eksplisit.
  6. Baris 35 (idPengguna?.let { "ID-$it" } ?: "TANPA-ID"): kombinasi ?.let dengan Elvis. Blok let mengembalikan String formatted, dan Elvis memberi nilai default bila idPengguna null.
  7. Baris 38 (konfigurasi["port"]?.let { it.toIntOrNull() ?: 8080 } ?: 8080): pola defensive parsing. Bila port null, Elvis luar memberi 8080. Bila port ada namun bukan angka valid, toIntOrNull() mengembalikan null dan Elvis dalam memberi 8080. Lapis ganda untuk keamanan maksimal.
  8. Baris 47 (hasil: Int = opsional?.let { it.length } ?: 0): ?.let mengembalikan nilai terakhir lambda sebagai hasil ekspresi. Hasil ini masih bertipe Int? karena blok hanya berjalan bila opsional tidak null. Elvis luar mengubahnya menjadi Int non-nullable.

Perbandingan dengan if (x != null) #

Bentuk ?.let dan if (x != null) sering kali dapat dipertukarkan. Pilihan antara keduanya biasanya didasarkan pada gaya dan konteks:

Aspek?.let { ... }if (x != null) { ... }
SintaksEkspresi nilai (lambda)Statement blok
Smart castParameter it non-nullableVariabel asli di-smart-cast
Mengembalikan nilaiYa, hasil lambdaHanya jika if dipakai sebagai ekspresi
Cocok untukTransformasi ringkas, chainBeberapa statement sekuensial
Pengganti namaMudah via nama ->Tidak, tetap memakai variabel asli

Mengganti Nama Parameter it #

Saat blok let berisi beberapa baris atau saat konteks sudah punya variabel dengan nama serupa, parameter default it bisa jadi membingungkan. Kotlin memungkinkan kita memberi nama eksplisit pada parameter lambda dengan pola ?.let { nama -> ... }. Pemberian nama eksplisit membuat kode lebih mudah dibaca terutama saat blok cukup panjang atau saat it berpotensi tertukar dengan variabel lain di luar.

Pemilihan nama parameter sebaiknya mendeskripsikan peran nilai, bukan tipenya. Nama seperti pengguna, alamatEmail, atau idValid lebih informatif daripada it atau value. Konvensi ini sejalan dengan praktik penamaan variabel pada umumnya — tujuan utamanya adalah agar pembaca kode dapat memahami maksud tanpa harus melihat-lihat konteks di sekitarnya.

Saat Tidak Perlu Memakai ?.let #

Beberapa situasi tidak memerlukan ?.let dan sebaiknya memakai alternatif yang lebih ringkas. Bila tujuan hanya mengembalikan nilai default saat null, operator Elvis ?: lebih singkat dan langsung. Bila tujuan hanya memfilter elemen non-null pada koleksi, filterNotNull() lebih ekspresif. Bila kita perlu memanggil beberapa method pada objek non-nullable hasil smart cast, if (x != null) { ... } sering lebih jelas daripada ?.let karena smart cast bekerja langsung tanpa perlu parameter it.

Referensi Lanjutan #

  • Null-safety — bagian let untuk null handling pada panduan null-safety resmi

Setelah memahami let, langkah berikutnya adalah mengenal platform types — situasi khusus yang muncul saat kode Kotlin memanggil kode Java di mana informasi nullability tidak tersedia — topik yang akan kita bahas pada chapter Platform Types.