Tipe Nullable

Perbedaan String dan String? serta alasan null dinyatakan secara eksplisit.

Daftar isi

Setiap tipe referensi pada Kotlin terbagi menjadi dua kelompok, yaitu tipe yang tidak menerima null dan tipe yang memang mengizinkan nilai kosong. Pembedaan ini menjadi inti dari null safety, yaitu fitur bahasa yang memindahkan pemeriksaan null dari runtime ke waktu kompilasi. Topik ini membahas perbedaan String dan String?, cara compiler menolak null pada tipe non-nullable, smart cast setelah pemeriksaan null, serta alasan desain di balik ketegasan tersebut.

Tipe String dan String?#

Tipe String tanpa tanda tambahan hanya menerima teks yang pasti terisi, sedangkan String? dengan tanda tanya di akhir menandai nilai yang boleh kosong. Tanda tanya merupakan bagian dari tipe, sehingga compiler memperlakukan String dan String? sebagai dua tipe yang berbeda. Variabel bertipe String dijamin tidak pernah menampung null, sedangkan variabel bertipe String? dapat menampung teks maupun null. Program berikut memuat kedua tipe tersebut dalam satu program yang sama.

Buat berkas main.kt berikut, yang menyimpan nama pada tipe non-nullable dan julukan pada tipe nullable.

kotlin main.kt
fun main() {
    val nama: String = "Dewi"
    var julukan: String? = "Dee"

    println("Nama: $nama")
    println("Julukan: $julukan")

    julukan = null
    println("Julukan kini: $julukan")
}

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
Nama: Dewi
Julukan: Dee
Julukan kini: null

Variabel julukan dideklarasikan sebagai var bertipe String?, sehingga penggantiannya menjadi null diterima tanpa keluhan. Baris terakhir mencetak teks null, yang berarti variabel tersebut memang tidak menunjuk nilai apa pun. Kondisi ini sah karena tipenya mengizinkan, sedangkan nama yang bertipe String tidak dapat diganti menjadi null dalam kondisi apa pun.

Tabel berikut merangkum perbedaan kedua tipe tersebut.

TipeNilai yang diterimaContoh deklarasi
Stringteks yang pasti terisival nama: String = "Dewi"
String?teks atau nullvar julukan: String? = null

Perbedaan kedua tipe baru terasa ketika anggota tipe dipanggil. Pemanggilan nama.length sah karena nilainya dijamin terisi. Pemanggilan julukan.length ditolak compiler karena tidak ada jaminan teksnya ada, dan penanganannya wajib ditulis terlebih dahulu. Kotlin menyediakan operator khusus untuk penanganan tersebut, yaitu safe call dan elvis operator, dan keduanya dibahas pada topik Safe Call dan Elvis di dalam modul ini.

Compiler menolak null#

Null tidak dapat dipaksakan ke dalam tipe non-nullable, dan penolakannya terjadi sebelum program dijalankan. Bentuk pesan errornya perlu dikenali agar perbaikannya cepat dilakukan. Berkas berikut sengaja mengisi variabel bertipe String dengan null.

kotlin salah.kt
fun main() {
    val kota: String = null
    println(kota)
}
bash
# kompilasi berkas salah.kt untuk melihat pesan error dari compiler
kotlinc salah.kt -d salah.jar
text Keluaran
salah.kt:2:24: error: null cannot be a value of a non-null type 'String'.
    val kota: String = null
                       ^^^^

Pesan error null cannot be a value of a non-null type 'String' menyatakan aturannya dengan langsung, yaitu null bukan nilai yang sah untuk tipe String. Proses kompilasi berhenti, sehingga program semacam itu tidak pernah sampai dijalankan. Angka 2:24 pada awal pesan menunjuk baris 2 dan kolom 24, yaitu tempat null dituliskan. Perbaikannya mengikuti niat yang dimaksud, yaitu mengubah tipenya menjadi String? apabila nilai memang boleh kosong, atau memberikan teks yang sesungguhnya apabila variabel dijamin terisi. Penolakan dini seperti ini murah karena ditemukan saat kompilasi, bukan oleh pengguna yang sedang menjalankan aplikasi.

Smart cast setelah pemeriksaan null#

Nilai nullable tetap dapat dipakai seperti biasa setelah kekosongannya diperiksa. Ketika pemeriksaan != null bernilai benar, compiler melakukan smart cast, yaitu memperlakukan nilai tersebut sebagai String tanpa konversi apa pun di dalam blok pemeriksaan. Tidak ada pernyataan tambahan yang ditulis, dan tidak ada variabel baru yang dibuat. Program berikut menerapkan pola ini pada fungsi yang menerima parameter String?.

kotlin safecast.kt
fun cetakPanjang(nilai: String?) {
    if (nilai != null) {
        println("Panjang: ${nilai.length}")
    } else {
        println("Nilainya kosong")
    }
}

fun main() {
    cetakPanjang("Budi")
    cetakPanjang(null)
}
bash
# kompilasi berkas safecast.kt menjadi safecast.jar, lalu jalankan programnya
kotlinc safecast.kt -d safecast.jar && java -cp "safecast.jar:$STDLIB" SafecastKt
text Keluaran
Panjang: 4
Nilainya kosong

Di dalam cabang if, pemanggilan nilai.length ditulis tanpa operator tambahan karena compiler mengetahui nilai sudah diperiksa tidak kosong. Cabang else menampung kondisi sebaliknya, dan di dalamnya nilai tersebut diketahui kosong. Smart cast berlaku sampai akhir blok tempat pemeriksaannya berada, sehingga pemanggilan berulang di dalam blok yang sama tidak perlu diperiksa lagi. Mekanisme ini menghilangkan pemeriksaan berulang yang membosankan, sekaligus menjaga aturan nullability tetap ditegakkan oleh compiler, bukan oleh disiplin pengembang.

Alasan null dinyatakan eksplisit#

Bahasa yang tidak membedakan nullability memperlakukan setiap referensi sebagai nilai yang mungkin kosong, dan Java termasuk dalam kelompok ini. Variabel referensi apa pun pada Java dapat bernilai null, sehingga satu-satunya perlindungan adalah pemeriksaan yang ditulis dan dijaga secara manual. Pemeriksaan yang terlewat baru ketahuan pada waktu program berjalan dalam bentuk NullPointerException, yaitu exception yang muncul ketika anggota dari nilai null dipanggil.

Kotlin mengambil arah yang berbeda dengan menjadikan nullability bagian dari tipe. Nilai yang mungkin kosong ditandai secara eksplisit dengan tanda tanya, dan setiap pemakaiannya wajib ditangani sebelum program dikompilasi. Kesalahan yang pada Java baru ditemukan pengguna pada runtime menjadi error kompilasi yang dilihat pengembang di awal. NullPointerException tetap dapat muncul pada Kotlin, tetapi hanya melalui jalur yang ditulis dengan sengaja seperti operator !! atau pemanggilan kode Java yang tipenya belum dianotasi, dan kedua jalur itu dibahas pada bagian lain modul ini.

Ketegasan ini juga mempertegas kontrak antarbagian program. Tipe pada parameter dan tipe kembalian menyatakan dengan pasti apakah nilai kosong merupakan kemungkinan yang sah, sehingga pemanggil tidak perlu menduga-duga. Data yang berpindah antarfungsi membawa janji yang dapat diperiksa compiler, bukan asumsi yang tidak tertulis di mana pun. Biaya menulis tanda tanyanya kecil, sedangkan kesalahan yang dicegahnya sering berat.

Ringkasan#

  • Tipe dengan tanda tanya seperti String? menerima null, sedangkan tipe tanpa tanda tanya seperti String menolaknya, dan keduanya diperlakukan sebagai tipe yang berbeda.
  • Pengisian null pada tipe non-nullable dihentikan compiler pada waktu kompilasi dengan pesan error yang menunjuk baris dan kolomnya.
  • Smart cast memperlakukan nilai nullable yang telah lolos pemeriksaan != null sebagai tipe non-null di dalam blok pemeriksaannya.
  • Nullability menjadi bagian dari tipe, sehingga risiko NullPointerException bergeser dari waktu berjalan ke waktu kompilasi.

Referensi#