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:
| Bentuk | Arti | Hasil |
|---|---|---|
x == null | Apakah x menyimpan null? | true bila x adalah null |
x != null | Apakah x tidak null? | true bila x memiliki nilai |
x === null | Sama 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 #
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:
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: 30Mari kita uraikan baris-baris penting pada contoh di atas:
- Baris 3-7 (
if (nama != null) { ... }): pola paling dasar. Setelah pengecekan!= null, kompilator menerapkan smart cast sehingganamadiperlakukan sebagaiStringnon-nullable. Karena itunama.lengthdannama.uppercase()dapat dipanggil langsung tanpa?.. - Baris 9-11 (
if (namaKosong == null)): bentuk kebalikan, memeriksa apakah variabel memang berisinull. Berguna untuk cabang penanganan kasus kosong. - Baris 14 (
if (umur != null) umur else 0):ifsebagai ekspresi yang menghasilkan nilai. Hasilnya bertipeIntnon-nullable. Pola ini setara denganumur ?: 0namun lebih eksplisit. - Baris 23 (
data != null && data.length > 5): smart cast bekerja pada kondisi gabungan. Cabang kanandata.length > 5diizinkan karenadatasudah dipersempit menjadiStringoleh cabang kiri. - Baris 33-37 (iterasi
for (item in daftar)): smart cast bekerja pada variabel loop. Setelahitem != null,item.lengthdapat dipanggil langsung di dalam blokif. - Baris 41-46 (
if (status != null) { when (status) ... }): smart cast menyebar ke dalamwhenyang berada di dalam blokif. Setiap cabangwhentetap memperlakukanstatussebagaiStringnon-nullable. - Baris 49-52 (
id.toString()): smart cast juga membuka akses method non-nullable.id.toString()mengembalikanStringkarenaidsudah dipersempit menjadiLong. - Baris 55 (
if (angka != null) angka * 2 else -1): pola nilai-default viaif-ekspresi. Sama denganangka?.times(2) ?: -1namun sintaks lebih familiar. - Baris 59 (
val adaCatatan: Boolean = catatan != null): hasil pengecekan null dapat disimpan sebagaiBoolean. Berguna untuk dokumentasi status tanpa harus menyimpan nilai aslinya. - Baris 62-65 (
if (nilaiA != null && nilaiB != null)): smart cast untuk dua variabel. Setelah kedua pengecekan lulus,nilaiA + nilaiBdapat dihitung tanpa?..
Perbandingan dengan Operator Elvis dan Safe Call #
| Pola | Output | Cocok untuk |
|---|---|---|
if (x != null) x.foo() else null | T? | Bila butuh cabang eksplisit |
x?.foo() | T? | Sintaks singkat |
x ?: default | T non-nullable | Bila butuh nilai pengganti |
if (x != null) x.foo() else default | T non-nullable | Sama 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.
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)— melemparIllegalArgumentExceptionbila null. Cocok untuk validasi argumen di awal function.checkNotNull(value)— melemparIllegalStateExceptionbila null. Cocok untuk memastikan state internal sudah benar.
Kedua function ini mengembalikan nilai non-nullable, sehingga smart cast otomatis diterapkan pada ekspresi berikutnya.
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:
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
nulleksplisit denganif (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.