Sistem null-safety Kotlin sempurna hanya ketika seluruh kode ditulis dalam Kotlin. Saat kita memanggil kode Java — misalnya standard library Java, framework Spring, atau library Android — kompilator Kotlin tidak memiliki informasi nullability yang lengkap. Kompilator menandai tipe dari Java sebagai platform type (ditulis T! secara internal) yang berarti tipe tersebut bisa nullable maupun non-nullable — kompilator tidak tahu pasti. Pada chapter ini kita akan membedah perilaku platform types, risiko yang ditimbulkannya, dan strategi mitigasi yang dianjurkan.
Apa Itu Platform Type #
Platform type muncul karena Java tidak memiliki anotasi nullability bawaan pada tipe referensi. Saat method Java String getNama() dipanggil dari Kotlin, kompilator tidak tahu apakah nilai kembalian bisa null atau tidak. Karena itu, kompilator memperlakukannya sebagai T! — sebuah tipe khusus yang dapat ditugaskan ke T (non-nullable) atau T? (nullable) tanpa peringatan.
Hal ini bermakna ganda:
- Memberi fleksibilitas: kita dapat menetapkan hasil panggilan Java ke variabel non-nullable tanpa konversi eksplisit.
- Membuka celah NPE: bila ternyata nilai dari Java adalah
nullnamun kita tetap memperlakukannya sebagai non-nullable,NullPointerExceptionakan muncul saat runtime.
Demonstrasi Platform Type #
Berikut adalah contoh yang memperlihatkan perilaku platform type menggunakan Java standard library. Method seperti System.getProperty(key), Properties.getProperty(key), dan HashMap.get(key) semuanya mengembalikan tipe platform dari sudut pandang Kotlin.
fun main() {
val properti = java.util.Properties()
properti.setProperty("host", "localhost")
properti.setProperty("port", "8080")
val host: String? = properti.getProperty("host")
println("Host: ${host?.length}")
val tidakAda: String? = properti.getProperty("tidak_ada")
println("Properti tidak_ada: $tidakAda")
println("Panjang (pakai safe call): ${tidakAda?.length}")
val peta = java.util.HashMap<String, String>()
peta["nama"] = "Budi"
val nama: String? = peta["nama"]
println("Nama dari HashMap: ${nama?.uppercase()}")
val umur: String? = peta["umur"]
println("Umur dari HashMap: $umur")
val panjangUmur: Int = umur?.length ?: 0
println("Panjang umur default: $panjangUmur")
val envPath: String? = System.getProperty("line.separator")
println("Line separator ditemukan: ${envPath != null}")
val tokenAman: String = System.getProperty("app.token") ?: "DEFAULT_TOKEN"
println("Token aplikasi: $tokenAman")
val sumber: String? = "Budi"
val panjangAman: Int = sumber?.length ?: 0
println("Panjang sumber: $panjangAman")
val pembatas: String = System.getProperty("file.separator") ?: "/"
println("Pembatas file: $pembatas")
val daftarHasil: List<String> = listOf("a", null, "b").mapNotNull { it?.uppercase() }
println("Daftar hasil tanpa null: $daftarHasil")
val konfigurasi: Map<String, String?> = mapOf("host" to "localhost", "port" to null)
val portFinal: String = konfigurasi["port"] ?: "8080"
println("Port final: $portFinal")
val daftarJava = java.util.ArrayList<String>()
daftarJava.add("Bandung")
val elemen: String? = if (daftarJava.isNotEmpty()) daftarJava[0] else null
println("Elemen dari ArrayList: $elemen")
val hasilAkhir: String = peta["tidak_ada"] ?: run {
println("Menggunakan nilai fallback karena kunci tidak ada")
"fallback"
}
println("Hasil akhir: $hasilAkhir")
}Output yang Dihasilkan:
Host: 9
Properti tidak_ada: null
Panjang (pakai safe call): null
Nama dari HashMap: BUDI
Umur dari HashMap: null
Panjang umur default: 0
Line separator ditemukan: true
Token aplikasi: DEFAULT_TOKEN
Panjang sumber: 4
Pembatas file: /
Daftar hasil tanpa null: [A, B]
Port final: 8080
Elemen dari ArrayList: Bandung
Menggunakan nilai fallback karena kunci tidak ada
Hasil akhir: fallbackMari kita uraikan baris-baris penting pada contoh di atas:
- Baris 5 (
properti.getProperty("host")): walaupun secara statis bertipe platformString!, kita deklarasikan variabel sebagaiString?eksplisit. Strategi ini aman — kompilator mengizinkan penetapan platform type ke nullable, dan kita terhindar dari risiko NPE. - Baris 9 (
properti.getProperty("tidak_ada")): kunci yang tidak ada membuat method Java mengembalikannull. Karena kita sudah mengetik variabel sebagaiString?, hasilnya konsistennulltanpa kejutan. - Baris 13 (
peta["nama"]):java.util.HashMap.get()mengembalikanV!. Tetap diperlakukan sebagai nullable sehingga.uppercase()dipanggil via safe call?.. - Baris 19 (
System.getProperty("line.separator")):System.getProperty()adalah salah satu method Java paling sering dipakai. Hasilnya platform type. Karena itu deklarasikan sebagaiString?eksplisit. - Baris 22 (
System.getProperty("app.token") ?: "DEFAULT_TOKEN"): pola yang aman untuk membaca konfigurasi dari sistem Java. Operator Elvis memberi nilai pengganti bila properti tidak terdaftar. - Baris 28 (
System.getProperty("file.separator") ?: "/"): bahkan untuk properti yang biasanya selalu ada (sepertifile.separatoryang bernilai/di Linux), tetap disarankan menambahkan Elvis sebagai fallback. Hal ini membuat kode lebih portable antar platform. - Baris 40 (
peta["tidak_ada"] ?: run { ... }): pola lebih kompleks denganrundi sisi kanan Elvis. Saat HashMap tidak mengandung kunci, blokrundieksekusi untuk menghasilkan nilai fallback sekaligus mencatat log.
Strategi Mitigasi #
Berikut tiga strategi yang dianjurkan untuk menangani platform types:
| Strategi | Cara | Kelebihan |
|---|---|---|
| Tipe eksplisit nullable | Deklarasikan variabel sebagai T? | Aman, eksplisit, mengikuti asumsi pesimis |
| Anotasi nullability Java | Tambahkan @Nullable/@NotNull pada kode Java | Kompilator Kotlin membaca anotasi dan memperlakukan tipe sesuai |
| Wrapper Kotlin | Bungkus pemanggilan Java dalam function Kotlin yang mengembalikan T? | Memisahkan batas Java-Kotlin menjadi jelas |
Referensi Lanjutan #
- Null-safety — bagian platform types dari kode Java pada panduan null-safety
Setelah memahami platform types, langkah berikutnya adalah menyusun rangkuman praktik terbaik untuk penanganan nullable di seluruh kodebase Kotlin — topik yang akan kita bahas pada chapter Null Safety Best Practices.