Sistem tipe Kotlin membedakan String (non-nullable) dan String? (nullable) secara ketat di level kompilator. Namun Java tidak memiliki perbedaan ini — semua tipe referensi dapat bernilai null. Saat Kotlin memanggil method Java, kompilator tidak tahu apakah hasilnya bisa null atau tidak, sehingga menciptakan tipe bayangan T! yang disebut platform type. Bab ini membahas asal mula platform type, risiko NullPointerException tersekat yang ditimbulkannya, dan strategi mitigasi melalui deklarasi tipe eksplisit nullable.
Asal Mula Platform Type #
Saat Kotlin memanggil method Java yang mengembalikan tipe referensi, kompilator tidak dapat menentukan nullability. Alasannya: Java tidak mengannotasi tipe kembalian secara default. Hasilnya adalah T! — tipe yang dapat diperlakukan baik sebagai T maupun sebagai T? di sisi Kotlin.
| Sumber | Tipe di Kotlin | Dapat null? |
|---|---|---|
| Method Java tanpa anotasi | T! (platform type) | Tidak diketahui |
Method Java beranotasi @Nullable | T? | Ya |
Method Java beranotasi @NotNull | T | Tidak |
| Method Kotlin sendiri | T atau T? (eksplisit) | Sesuai deklarasi |
Risiko platform type: kompilator tidak memperingatkan bila kita menugaskan hasil method Java ke variabel non-nullable. Bila method Java ternyata mengembalikan null, operasi pada variabel tersebut akan melempar NullPointerException saat runtime — persis seperti di Java.
Contoh Lengkap: Risiko NPE dari Platform Type #
Kode berikut membandingkan dua pendekatan — tanpa mitigasi (rawan NPE) dan dengan mitigasi (deklarasi eksplisit nullable):
import java.util.ArrayList
import java.util.HashMap
// Demonstrasi platform types (T!) yang muncul saat Kotlin memakai tipe dari Java.
// Kotlin tidak tahu apakah Java akan mengembalikan null atau tidak,
// sehingga tipe tersebut menjadi "platform type" yang dapat diperlakukan
// baik nullable maupun non-nullable — di sinilah letak risiko NPE.
// ArrayList.get(i) di Java mengembalikan E tanpa @Nullable annotation,
// sehingga Kotlin memperlakukannya sebagai platform type E!
fun ambilElemen(daftar: ArrayList<String>, indeks: Int): String {
// get(int) mengembalikan platform type String! — kompilator TIDAK memperingatkan
val nilai: String = daftar[indeks]
// Bila indeks di luar batas, Java melempar IndexOutOfBoundsException;
// bila elemen null, baris berikut akan melempar NPE tanpa peringatan.
return nilai.uppercase()
}
fun ambilElemenAman(daftar: ArrayList<String?>, indeks: Int): String? {
// Deklarasi eksplisit sebagai String? — Kotlin memperlakukan dengan benar
val nilai: String? = daftar[indeks]
return nilai?.uppercase()
}
fun main() {
// 1. Akses elemen yang ada — aman
val daftar: ArrayList<String> = ArrayList()
daftar.add("Budi")
daftar.add("Siti")
println("=== Aman: indeks dalam batas ===")
println("ambilElemen(daftar, 0) : ${ambilElemen(daftar, 0)}")
// 2. Akses elemen null di list String? — risiko NPE
println()
println("=== Berisiko: elemen null ===")
val daftarNull: ArrayList<String?> = ArrayList()
daftarNull.add("Pertama")
daftarNull.add(null)
daftarNull.add("Ketiga")
val aman0: String? = ambilElemenAman(daftarNull, 0)
val aman1: String? = ambilElemenAman(daftarNull, 1)
println("ambilElemenAman(daftarNull, 0) : $aman0")
println("ambilElemenAman(daftarNull, 1) : $aman1")
// 3. HashMap.get() mengembalikan String? di Kotlin (jdk beri @Nullable)
println()
println("=== HashMap.get(): sudah nullable ===")
val peta: HashMap<String, String> = HashMap()
peta["nama"] = "Budi"
val nilaiNama: String? = peta["nama"]
val nilaiTidakAda: String? = peta["tidakAda"]
println("peta[\"nama\"] : $nilaiNama")
println("peta[\"tidakAda\"] : $nilaiTidakAda")
// 4. Mitigasi: deklarasi tipe eksplisit nullable
println()
println("=== Mitigasi: tipe eksplisit nullable ===")
val hasilAman: String = nilaiTidakAda ?: "(tidak ditemukan)"
println("nilaiTidakAda ?: default : $hasilAman")
// 5. Iterasi ArrayList<String?> dengan smart-cast
println()
println("=== Iterasi dengan smart-cast ===")
for (i in 0 until daftarNull.size) {
val elemen: String? = daftarNull[i]
if (elemen != null) {
println("[$i] elemen non-null: ${elemen.uppercase()}")
} else {
println("[$i] elemen null, lewati")
}
}
}Output yang Dihasilkan:
=== Aman: indeks dalam batas ===
ambilElemen(daftar, 0) : BUDI
=== Berisiko: elemen null ===
ambilElemenAman(daftarNull, 0) : PERTAMA
ambilElemenAman(daftarNull, 1) : null
=== HashMap.get(): sudah nullable ===
peta["nama"] : Budi
peta["tidakAda"] : null
=== Mitigasi: tipe eksplisit nullable ===
nilaiTidakAda ?: default : (tidak ditemukan)
=== Iterasi dengan smart-cast ===
[0] elemen non-null: PERTAMA
[1] elemen null, lewati
[2] elemen non-null: KETIGAMari kita telaah baris-baris kode di atas:
- Baris 12 (
val nilai: String = daftar[indeks]):daftar[indeks]adalah platform typeString!. Kompilator Kotlin membiarkan penugasan keStringnon-nullable tanpa peringatan. Bila elemen yang diambil ternyatanull, barisnilai.uppercase()akan melemparNullPointerExceptionsaat runtime — kesalahan yang seharusnya dapat dicegah di kompilasi bila tipe nullable eksplisit. - Baris 19 (
fun ambilElemenAman(daftar: ArrayList<String?>, indeks: Int): String?): parameter memakaiArrayList<String?>(elemen nullable) dan tipe kembalianString?. Deklarasi eksplisit ini memaksa smart-cast dan safe-call?.di badan fungsi, menghilangkan risiko NPE. - Baris 36-39 (HashMap.get sudah nullable): JDK modern mengannotasi
Map.getdengan@Nullable, sehingga Kotlin membaca sebagaiString?bukan platform type. Inilah bukti bahwa anotasi nullability di sisi Java benar-benar mengubah perilaku Kotlin. Untuk library Java lawas tanpa anotasi, hasil get tetap platform type. - Baris 47 (
nilaiTidakAda ?: "(tidak ditemukan)"): elvis operator?:memberi nilai default bila kirinull. Ini idiom Kotlin standar untuk mengelola nullable dari Java tanpa melempar NPE. - Baris 53-59 (smart-cast setelah null check): setelah
if (elemen != null), kompilator smart-castelemenkeStringnon-nullable sehingga.uppercase()dapat dipanggil tanpa?.. Smart-cast adalah kompensasi utama Kotlin atas risiko platform type.
Strategi Mitigasi Platform Type #
Tiga strategi utama untuk menghindari NPE dari platform type:
| Strategi | Implementasi | Kapan Dipakai |
|---|---|---|
| Deklarasi eksplisit nullable | val x: String? = methodJava() | Tidak yakin nullability |
| Elvis operator default | val x = methodJava() ?: "" | Punya nilai default |
| Safe-call operator | methodJava()?.uppercase() | Operasi berantai pada nullable |
Anotasi Java @Nullable/@NotNull | tambahkan di kode Java | Bila kita memilikinya |
Referensi Lanjutan #
- Platform types — bagian khusus platform types pada dokumentasi Java interop Kotlin
- Calling Java from Kotlin — dokumentasi utama Java interop Kotlin termasuk platform types
Selanjutnya #
Solusi permanen untuk platform type adalah menambahkan anotasi nullability di sisi Java. Bab Nullability Annotations akan membahas @Nullable, @NotNull, JSR-305, dan bagaimana Kotlin membaca anotasi tersebut untuk menentukan tipe yang tepat.