Pengecekan null secara eksplisit adalah pola paling dasar untuk menangani tipe nullable. Kotlin menggunakan operator == dan != untuk membandingkan dengan null — bukan === seperti pada beberapa bahasa lain. Setelah pengecekan != null, kompilator menerapkan smart cast sehingga variabel nullable berubah menjadi non-nullable di dalam blok. Pada chapter ini kita akan membedah pola if (x != null), perbandingan dengan operator Elvis, dan situasi praktis seperti iterasi list dengan elemen nullable.

Operator == dan != untuk Null #

Kotlin memperlakukan == sebagai pemanggilan equals() bila penerima non-nullable, namun untuk perbandingan dengan null literal, kompilator menghasilkan instruksi khusus ifnull/ifnonnull` di bytecode. Berikut ringkasan:

BentukArtiHasil
x == nullApakah x menyimpan null?true bila x adalah null
x != nullApakah x tidak null?true bila x memiliki nilai
x === nullSama dengan == null (referensial)Identik dengan di atas

Untuk perbandingan dengan null, == dan === menghasilkan bytecode yang sama. Konvensi yang dianjurkan adalah memakai == dan != agar kode lebih ringkas dan konsisten dengan perbandingan nilai lain.

Pola if (x != null) dengan Smart Cast #

kotlin
fun main() {
    val nama: String? = "Budi"
    if (nama != null) {
        println("Nama: $nama")
        println("Panjang nama: ${nama.length}")
        println("Huruf besar: ${nama.uppercase()}")
    }

    val namaKosong: String? = null
    if (namaKosong == null) {
        println("namaKosong ternyata null")
    }

    val umur: Int? = 25
    val umurFinal: Int = if (umur != null) umur else 0
    println("Umur final: $umurFinal")

    val input: String? = null
    if (input == null) {
        println("Input kosong, menggunakan default")
    }
    val nilaiInput: String = input ?: "default"
    println("Nilai input: $nilaiInput")

    val data: String? = "Halo Dunia"
    if (data != null && data.length > 5) {
        println("Data panjang: ${data.length} karakter")
    }

    val token: String? = null
    val tokenAman: String = token ?: "DEFAULT_TOKEN"
    println("Token aman: $tokenAman")

    val daftar: List<String?> = listOf("Budi", null, "Siti")
    for (item in daftar) {
        if (item != null) {
            println("Item valid: $item, panjang ${item.length}")
        } else {
            println("Item null dilewati")
        }
    }

    val status: String? = "aktif"
    if (status != null) {
        when (status) {
            "aktif" -> println("Status aktif, panjang ${status.length}")
            "nonaktif" -> println("Status nonaktif")
            else -> println("Status tidak dikenal: $status")
        }
    }

    val id: Long? = 1234L
    if (id != null) {
        val idString: String = id.toString()
        println("ID sebagai string: $idString, panjang ${idString.length}")
    }

    val angka: Int? = null
    val hasil: Int = if (angka != null) angka * 2 else -1
    println("Hasil angka: $hasil")

    val catatan: String? = null
    val adaCatatan: Boolean = catatan != null
    println("Ada catatan? $adaCatatan")

    val nilaiA: Int? = 10
    val nilaiB: Int? = 20
    if (nilaiA != null && nilaiB != null) {
        println("Jumlah: ${nilaiA + nilaiB}")
    }
}

Output yang Dihasilkan:

text
Nama: Budi
Panjang nama: 4
Huruf besar: BUDI
namaKosong ternyata null
Umur final: 25
Input kosong, menggunakan default
Nilai input: default
Data panjang: 10 karakter
Token aman: DEFAULT_TOKEN
Item valid: Budi, panjang 4
Item null dilewati
Item valid: Siti, panjang 4
Status aktif, panjang 5
ID sebagai string: 1234, panjang 4
Hasil angka: -1
Ada catatan? false
Jumlah: 30

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

  1. Baris 3-7 (if (nama != null) { ... }): pola paling dasar. Setelah pengecekan != null, kompilator menerapkan smart cast sehingga nama diperlakukan sebagai String non-nullable. Karena itu nama.length dan nama.uppercase() dapat dipanggil langsung tanpa ?..
  2. Baris 9-11 (if (namaKosong == null)): bentuk kebalikan, memeriksa apakah variabel memang berisi null. Berguna untuk cabang penanganan kasus kosong.
  3. Baris 14 (if (umur != null) umur else 0): if sebagai ekspresi yang menghasilkan nilai. Hasilnya bertipe Int non-nullable. Pola ini setara dengan umur ?: 0 namun lebih eksplisit.
  4. Baris 23 (data != null && data.length > 5): smart cast bekerja pada kondisi gabungan. Cabang kanan data.length > 5 diizinkan karena data sudah dipersempit menjadi String oleh cabang kiri.
  5. Baris 33-37 (iterasi for (item in daftar)): smart cast bekerja pada variabel loop. Setelah item != null, item.length dapat dipanggil langsung di dalam blok if.
  6. Baris 41-46 (if (status != null) { when (status) ... }): smart cast menyebar ke dalam when yang berada di dalam blok if. Setiap cabang when tetap memperlakukan status sebagai String non-nullable.
  7. Baris 49-52 (id.toString()): smart cast juga membuka akses method non-nullable. id.toString() mengembalikan String karena id sudah dipersempit menjadi Long.
  8. Baris 55 (if (angka != null) angka * 2 else -1): pola nilai-default via if-ekspresi. Sama dengan angka?.times(2) ?: -1 namun sintaks lebih familiar.
  9. Baris 59 (val adaCatatan: Boolean = catatan != null): hasil pengecekan null dapat disimpan sebagai Boolean. Berguna untuk dokumentasi status tanpa harus menyimpan nilai aslinya.
  10. Baris 62-65 (if (nilaiA != null && nilaiB != null)): smart cast untuk dua variabel. Setelah kedua pengecekan lulus, nilaiA + nilaiB dapat dihitung tanpa ?..

Perbandingan dengan Operator Elvis dan Safe Call #

PolaOutputCocok untuk
if (x != null) x.foo() else nullT?Bila butuh cabang eksplisit
x?.foo()T?Sintaks singkat
x ?: defaultT non-nullableBila butuh nilai pengganti
if (x != null) x.foo() else defaultT non-nullableSama dengan Elvis, lebih verbose

Untuk pengecekan sederhana, ?. dan ?: biasanya lebih ringkas. Namun bila blok kode setelah pengecekan cukup panjang atau membutuhkan beberapa statement, if (x != null) { ... } sering kali lebih mudah dibaca.

Batasan Smart Cast #

Smart cast tidak selalu bisa diterapkan. Kompilator menolak smart cast bila variabel bisa berubah di antara titik pengecekan dan titik pemakaian. Kasus paling umum adalah property var pada class — karena thread lain bisa mengubah nilainya setelah pengecekan != null.

kotlin Contoh: smart cast gagal
class Repo {
    var nama: String? = null  // var — bisa berubah kapan saja

    fun cetak() {
        if (nama != null) {
            // println(nama.length) // ERROR: smart cast tidak berlaku pada var
            println(nama!!.length)  // eksplisit — tetap berisiko
            // atau simpan ke val lokal:
            val n = nama
            if (n != null) println(n.length)  // OK, smart cast pada val
        }
    }
}

Pola idiomatik adalah menyimpan nilai ke val lokal terlebih dahulu, lalu mengecek != null pada val tersebut. Lokal val tidak bisa diubah, sehingga kompilator menjamin smart cast aman.

Pola requireNotNull dan checkNotNull #

Selain if (x != null), Kotlin menyediakan dua function bawaan untuk validasi null:

  • requireNotNull(value) — melempar IllegalArgumentException bila null. Cocok untuk validasi argumen di awal function.
  • checkNotNull(value) — melempar IllegalStateException bila null. Cocok untuk memastikan state internal sudah benar.

Kedua function ini mengembalikan nilai non-nullable, sehingga smart cast otomatis diterapkan pada ekspresi berikutnya.

kotlin Contoh: requireNotNull dan checkNotNull
fun prosesPengguna(id: Long?, nama: String?): String {
    requireNotNull(id) { "id wajib diisi" }       // → IllegalArgumentException
    requireNotNull(nama) { "nama wajib diisi" }
    return "Pengguna $id: $nama (panjang ${nama.length})"
}

class Session {
    private var token: String? = null

    fun aksesApi(): String {
        checkNotNull(token) { "Token belum di-set, login dulu" }  // → IllegalStateException
        return "Authorization: Bearer $token"
    }
}

Pemilihan requireNotNull vs checkNotNull mengikuti semantik: require untuk argumen dari pemanggil, check untuk state internal class.

Pola Iterasi List Nullable #

Iterasi List<String?> dengan elemen nullable membutuhkan pengecekan di setiap item. Kotlin menyediakan beberapa alternatif:

kotlin Pola iterasi list nullable
val nama: List<String?> = listOf("Budi", null, "Siti", null, "Andi")

// 1. if (item != null) — verbose, eksplisit
for (item in nama) {
    if (item != null) println(item.length)
}

// 2. filterNotNull() — buang null lebih dulu, lalu iterasi
nama.filterNotNull().forEach { println(it.length) }

// 3. forEach + safe call — ringkas
nama.forEach { it?.let { nama -> println(nama.length) } }

// 4. mapNotNull — transformasi + filter sekaligus
val panjang = nama.mapNotNull { it?.length }  // List<Int>

Pilihan tergantung konteks: filterNotNull() mudah dibaca untuk filter murni, mapNotNull efisien bila butuh transformasi sekaligus.

Setelah memahami pola pengecekan null #

Referensi Lanjutan #

  • Null-safety — panduan resmi pengecekan null eksplisit dengan if (x != null)

Setelah memahami pola pengecekan null, langkah berikutnya adalah mempelajari scope function let yang menjadi alternatif ringkas untuk mengeksekusi blok kode hanya bila nilai tidak null — topik yang akan kita bahas pada chapter let untuk Null Handling.