NullPointerException adalah galat yang paling sering muncul pada ekosistem JVM dan biasanya baru terdeteksi saat program sudah berjalan. Kotlin mengatasinya dengan membagi tipe referensi menjadi dua kategori sejak compile-time: tipe non-nullable seperti String yang tidak boleh menyimpan null, dan tipe nullable seperti String? yang diizinkan menyimpan null. Pembedaan ini dilakukan langsung oleh kompilator sehingga banyak potensi galat dapat ditangkap sebelum program dijalankan. Pada chapter ini kita akan membedah perbedaan kedua kategori tersebut, cara mendeklarasikannya, dan implikasinya terhadap keamanan kode.

Perbedaan Tipe Non-Nullable dan Nullable #

Tipe non-nullable adalah perilaku bawaan Kotlin untuk semua tipe referensi. Saat kita menulis val nama: String = "Budi", kompilator menjamin variabel nama tidak akan pernah berisi null. Setiap upaya menetapkan null ke nama akan ditolak saat kompilasi. Sebaliknya, tipe nullable ditandai dengan tanda tanya di akhir tipe: String?. Tipe ini mengizinkan nilai null sebagai nilai yang sah.

Berikut ringkasan perbedaan kedua kategori:

AspekString (non-nullable)String? (nullable)
Boleh null?Tidak, compile errorYa
Akses .length langsung?Aman, tidak butuh pengecekanTidak aman, perlu ?. atau cek != null
TujuanData wajib adaData opsional atau belum pasti
Risiko NPE saat runtimeHampir nol jika tidak ada interop JavaPerlu ditangani eksplisit

Deklarasi dan Penggunaan Dasar #

Mari kita amati contoh konkret yang memperlihatkan kedua kategori tipe berdampingan:

kotlin
fun main() {
    val namaNonNullable: String = "Budi"
    println("Nama non-nullable: $namaNonNullable")
    println("Panjang nama: ${namaNonNullable.length}")

    val namaNullable: String? = null
    println("Nama nullable: $namaNullable")

    val namaNullableBerisi: String? = "Siti"
    println("Nama nullable berisi: $namaNullableBerisi")
    println("Panjang (pakai safe call): ${namaNullableBerisi?.length}")
    println("Panjang (dari null): ${namaNullable?.length}")

    val daftar: List<String?> = listOf("Budi", null, "Dewi")
    println("Daftar dengan elemen null: $daftar")
    for (item in daftar) {
        println("Item: ${item ?: "<kosong>"}")
    }

    val tipeNonNullable: String = "Halo"
    val tipeNullable: String? = tipeNonNullable
    println("Konversi non-nullable ke nullable: $tipeNullable")

    val umur: Int = 25
    val umurNullable: Int? = umur
    println("Umur non-nullable: $umur, umur nullable: $umurNullable")

    val idPengguna: Long? = null
    val idDefault: Long = idPengguna ?: 0L
    println("ID pengguna (default 0): $idDefault")
}

Output yang Dihasilkan:

text
Nama non-nullable: Budi
Panjang nama: 4
Nama nullable: null
Nama nullable berisi: Siti
Panjang (pakai safe call): 4
Panjang (dari null): null
Daftar dengan elemen null: [Budi, null, Dewi]
Item: Budi
Item: <kosong>
Item: Dewi
Konversi non-nullable ke nullable: Halo
Umur non-nullable: 25, umur nullable: 25
ID pengguna (default 0): 0

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

  1. Baris 2 (val namaNonNullable: String = "Budi"): tipe String tanpa ? berarti variabel ini tidak boleh menyimpan null. Kompilator menjamin hal tersebut sejak compile-time.
  2. Baris 5 (val namaNullable: String? = null): tanda ? setelah String mengubah tipe menjadi nullable. Sekarang null adalah nilai yang sah.
  3. Baris 9 (namaNullableBerisi?.length): operator safe call ?. hanya memanggil .length bila namaNullableBerisi tidak null; bila null, hasil seluruh ekspresi menjadi null. Tanpa ?., akses .length langsung pada tipe nullable akan ditolak kompilator.
  4. Baris 13 (List<String?>): tipe elemen list juga dapat nullable. List ini berisi kombinasi String dan null.
  5. Baris 16 (item ?: "<kosong>"): operator Elvis ?: memberi nilai default saat item null. Topik ini akan kita bahas tuntas pada chapter Elvis Operator.
  6. Baris 19 (val tipeNullable: String? = tipeNonNullable): nilai non-nullable dapat ditugaskan ke variabel nullable tanpa konversi eksplisit karena tipe nullable adalah supertype dari tipe non-nullable. Arah sebaliknya tidak aman dan perlu pengecekan eksplisit.

Mengapa Pembedaan Ini Penting #

Pada bahasa seperti Java, semua referensi secara teknis bisa null. Setiap pemanggilan method seperti user.getName().length() berpotensi melempar NullPointerException bila salah satu titik di rantai tersebut ternyata null. Kotlin memindahkan kekhawatiran tersebut ke compile-time: jika kita ingin sebuah variabel bisa null, kita harus menyatakannya dengan eksplisit menggunakan ?, dan pemanggilan method pada variabel nullable wajib menggunakan operator ?. atau diawali dengan pengecekan != null.

Referensi Lanjutan #

  • Null-safety — panduan resmi perbedaan tipe nullable String? dan non-nullable String

Dengan pemahaman perbedaan kedua kategori tipe, langkah berikutnya adalah membahas cara mendeklarasikan variabel nullable dan mengubah nilainya sepanjang program — materi yang akan kita bahas pada chapter Nullable Variables.