let dan Pengembalian Awal

Fungsi scope let untuk penanganan null dan penggunaan !! yang jujur.

Daftar isi

Safe call dan elvis operator menyelesaikan sebagian besar kebutuhan penanganan null, tetapi Kotlin menyediakan susunan lain yang lebih luas untuk nilai yang mungkin kosong. Topik ini membahas fungsi scope let beserta hasilnya sebagai ekspresi, pola pengembalian awal (early return) dengan ?: return, serta pemakaian operator !! yang disertai pertimbangan jujur mengenai kapan tanda itu layak ditulis.

let untuk nilai nullable#

Fungsi let termasuk scope function, yaitu fungsi yang menjalankan blok kode dalam konteks sebuah nilai. Ketika dipanggil dengan safe call dalam bentuk nilai?.let { }, blok di dalamnya hanya dijalankan apabila nilai terisi, dan nilai itu tersedia di dalam blok dengan nama it. Apabila nilai null, pemanggilan dilewati begitu saja tanpa error. Program berikut membandingkan kedua keadaan tersebut dalam satu program.

Buat berkas main.kt berikut.

kotlin main.kt
fun main() {
    val catatan: String? = "Belanja mingguan"

    catatan?.let {
        println("Panjang catatan: ${it.length}")
        println("Huruf kapital: ${it.uppercase()}")
    }

    val kosong: String? = null
    kosong?.let {
        println("Baris ini tidak pernah dicetak")
    }

    println("Program selesai")
}

Dua baris export pada perintah berikut mengarahkan terminal ke compiler Kotlin yang dipakai jalur ini, dan jalurnya perlu disesuaikan dengan lokasi instalasi pada komputer lain.

bash
# compiler Kotlin dan pustaka standar yang dipakai jalur ini
export PATH=/home/kal/my-project/tools/kotlinc/bin:/home/kal/my-project/tools/jdk-merged/bin:$PATH
export STDLIB=/home/kal/my-project/tools/kotlinc/lib/kotlin-stdlib.jar
# kompilasi berkas main.kt menjadi out.jar, lalu jalankan programnya
kotlinc main.kt -d out.jar && java -cp "out.jar:$STDLIB" MainKt
text Keluaran
Panjang catatan: 16
Huruf kapital: BELANJA MINGGUAN
Program selesai

Blok let pada catatan menjalankan dua pernyataan, dengan it merujuk pada teks yang terisi. Blok yang sama pada kosong tidak dijalankan sama sekali, dan program berlanjut ke baris berikutnya tanpa pesan apa pun. Bentuk ini cocok untuk deretan langkah yang seluruhnya bergantung pada keberadaan nilai, karena pemeriksaannya cukup ditulis satu kali di depan blok. Bandingkan dengan pemeriksaan if terpisah yang mengulang kondisi pada setiap pemakaian, sedangkan let menjaga langkah-langkah itu tetap berkelompok dalam satu tempat.

Fungsi let juga sah dipanggil tanpa safe call pada nilai yang pasti terisi, dan bloknya selalu berjalan. Kombinasi ?.let justru memanfaatkan sifat safe call untuk mengubah kekosongan menjadi penundaan blok, sehingga pemeriksaan dan pemrosesannya menyatu dalam satu penulisan.

Hasil let sebagai ekspresi#

Sebagaimana if dan when, pemanggilan let menghasilkan nilai, yaitu nilai dari ekspresi terakhir di dalam bloknya. Sifat ini membuat let dapat dirangkai dengan elvis operator untuk memperoleh nilai yang pasti terisi dari nilai yang mungkin kosong.

kotlin ekspresi.kt
fun main() {
    val catatan: String? = "Belanja mingguan"

    val panjang = catatan?.let { it.length }
    println("Panjang: $panjang")

    val aman = catatan?.let { it.length } ?: 0
    println("Panjang aman: $aman")
}
bash
# kompilasi berkas ekspresi.kt menjadi ekspresi.jar, lalu jalankan programnya
kotlinc ekspresi.kt -d ekspresi.jar && java -cp "ekspresi.jar:$STDLIB" EkspresiKt
text Keluaran
Panjang: 16
Panjang aman: 16

Variabel panjang bertipe Int? karena hasil let pada nilai nullable tetap mungkin kosong. Variabel aman bertipe Int tanpa tanda tanya, karena elvis operator mengisi kekosongannya dengan 0. Keduanya mencetak angka yang sama pada contoh ini karena catatan terisi, dan perbedaannya baru terlihat apabila nilainya null. Bentuk ?.let { } ?: nilaiBawaan umum dipakai ketika pemrosesan nilainya lebih dari satu baris, sehingga pemeriksaan terpisah tidak diperlukan dan hasilnya langsung berupa nilai yang aman.

Apabila bloknya memuat beberapa pernyataan, nilai terakhirnya yang dikirim ke pemanggil, sedangkan pernyataan sebelumnya berperan sebagai langkah persiapan. Aturan yang sama berlaku pada pemanggilan let di mana pun, karena sifat ekspresinya berasal dari fungsi tersebut, bukan dari safe call di depannya.

Pengembalian awal dengan ?: return#

Pengembalian awal menghentikan fungsi lebih dahulu apabila syaratnya tidak terpenuhi, sehingga tubuh utama fungsi bebas dari sarang pemeriksaan yang dalam. Dengan elvis operator, nilai null dapat langsung dikembalikan ke pemanggil sebagai tanda tidak adanya pekerjaan yang bisa dilakukan. Program berikut memuat dua bentuknya, yaitu pengembalian tanpa nilai pada fungsi Unit dan pengembalian dengan nilai pada fungsi Int.

kotlin kembali.kt
fun cetakRingkasan(catatan: String?) {
    val teks = catatan ?: return
    println("Ringkasan: ${teks.uppercase()}")
}

fun panjangAman(nilai: String?): Int {
    val teks = nilai ?: return 0
    return teks.length
}

fun main() {
    cetakRingkasan("Belanja mingguan")
    cetakRingkasan(null)
    println(panjangAman("Statistik nilai"))
    println(panjangAman(null))
}
bash
# kompilasi berkas kembali.kt menjadi kembali.jar, lalu jalankan programnya
kotlinc kembali.kt -d kembali.jar && java -cp "kembali.jar:$STDLIB" KembaliKt
text Keluaran
Ringkasan: BELANJA MINGGUAN
15
0

Fungsi cetakRingkasan bertipe Unit, sehingga ?: return ditulis tanpa nilai dan fungsi berhenti apabila catatannya kosong. Fungsi panjangAman wajib mengirim Int, sehingga jalur kekosongannya mengirim 0. Pada kedua bentuk tersebut, baris setelah pemeriksaan bekerja dengan nilai yang sudah pasti terisi, tanpa safe call maupun pemeriksaan ulang. Pola ini menjaga fungsi tetap dangkal karena setiap kondisi keluar ditangani di awal, dan pembaca fungsi cukup mengikuti alur yang tersisa. Bentuk yang sama berlaku untuk tipe kembalian lain, cukup mengganti nilai yang dikirim pada jalur kekosongannya.

Urutan penulisan membuat pola ini mudah dikenali, yaitu pemeriksaan kekosongan diletakkan di awal fungsi, sebelum pekerjaan utamanya dimulai. Susunan semacam ini menghindarkan cabang bertingkat, karena setiap syarat keluar selesai ditangani pada baris-baris pertama. Fungsi yang panjang pun tetap dangkal, dan alur utamanya terbaca dari atas ke bawah tanpa lompatan.

Pemakaian !! secara jujur#

Operator !! yang dibaca not-null assertion menyatakan bahwa nilai di sebelah kirinya dijamin tidak null menurut penulis kode. Compiler menerima tanda itu, melepaskan pemeriksaan pada waktu kompilasi, dan memindahkan pengawasannya ke waktu program berjalan. Apabila asumsinya keliru, program berhenti. Berkas berikut sengaja memaksakan tanda itu pada nilai null.

kotlin seru.kt
fun main() {
    val catatan: String? = null
    val teks = catatan!!
    println(teks)
}
bash
# kompilasi berkas seru.kt, lalu jalankan dengan keluaran dibatasi tiga baris pertama
kotlinc seru.kt -d seru.jar && java -cp "seru.jar:$STDLIB" SeruKt 2>&1 | head -3
text Keluaran
Exception in thread "main" java.lang.NullPointerException
	at SeruKt.main(seru.kt:3)
	at SeruKt.main(seru.kt)

Program jatuh pada baris ketiga karena asumsi non-nullnya keliru, dan keluaran di atas hanya menampilkan tiga baris pertama dari stack trace. Exception yang muncul adalah NullPointerException, sehingga jenis error yang berusaha dicegah null safety dapat kembali masuk melalui pintu ini. Pemakaian !! yang layak dibenarkan terbatas pada keadaan yang kebenarannya dapat dibuktikan. Contohnya adalah kode pengujian yang memang berharap nilai tertentu terisi, atau alur yang jaminan non-nullnya terlihat lengkap di tempat yang sama. Bila jaminan itu ada, tuliskan alasannya sebagai komentar di sebelah !! agar pembaca berikutnya mengetahui dasarnya. Untuk keadaan lain, safe call, elvis operator, dan let memberikan hasil yang sama tanpa risiko program berhenti.

Ringkasan#

  • ?.let { } menjalankan blok kode hanya untuk nilai yang terisi, dengan nilai tersebut tersedia sebagai it.
  • Hasil let berupa ekspresi terakhir di dalam bloknya, sehingga dirangkai dengan elvis operator menghasilkan nilai yang pasti terisi.
  • ?: return menerapkan pengembalian awal, yaitu menghentikan fungsi di awal apabila nilainya kosong.
  • !! mengirim pemeriksaan null ke waktu berjalan, dan pemakaiannya perlu alasan yang tertulis.

Referensi#