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.

SumberTipe di KotlinDapat null?
Method Java tanpa anotasiT! (platform type)Tidak diketahui
Method Java beranotasi @NullableT?Ya
Method Java beranotasi @NotNullTTidak
Method Kotlin sendiriT 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):

kotlin
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:

text
=== 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: KETIGA

Mari kita telaah baris-baris kode di atas:

  1. Baris 12 (val nilai: String = daftar[indeks]): daftar[indeks] adalah platform type String!. Kompilator Kotlin membiarkan penugasan ke String non-nullable tanpa peringatan. Bila elemen yang diambil ternyata null, baris nilai.uppercase() akan melempar NullPointerException saat runtime — kesalahan yang seharusnya dapat dicegah di kompilasi bila tipe nullable eksplisit.
  2. Baris 19 (fun ambilElemenAman(daftar: ArrayList<String?>, indeks: Int): String?): parameter memakai ArrayList<String?> (elemen nullable) dan tipe kembalian String?. Deklarasi eksplisit ini memaksa smart-cast dan safe-call ?. di badan fungsi, menghilangkan risiko NPE.
  3. Baris 36-39 (HashMap.get sudah nullable): JDK modern mengannotasi Map.get dengan @Nullable, sehingga Kotlin membaca sebagai String? 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.
  4. Baris 47 (nilaiTidakAda ?: "(tidak ditemukan)"): elvis operator ?: memberi nilai default bila kiri null. Ini idiom Kotlin standar untuk mengelola nullable dari Java tanpa melempar NPE.
  5. Baris 53-59 (smart-cast setelah null check): setelah if (elemen != null), kompilator smart-cast elemen ke String non-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:

StrategiImplementasiKapan Dipakai
Deklarasi eksplisit nullableval x: String? = methodJava()Tidak yakin nullability
Elvis operator defaultval x = methodJava() ?: ""Punya nilai default
Safe-call operatormethodJava()?.uppercase()Operasi berantai pada nullable
Anotasi Java @Nullable/@NotNulltambahkan di kode JavaBila kita memilikinya

Referensi Lanjutan #

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.